-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[LANG-1771] Handle malformed array input '[String' in getShortCanonicalName #1391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[LANG-1771] Handle malformed array input '[String' in getShortCanonicalName #1391
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abhisripathi
Please remove "countUpperCaseLetters" as it has nothing to do with this PR.
@@ -237,6 +237,14 @@ public void test_getAllSuperclasses_Class() { | |||
assertNull(ClassUtils.getAllSuperclasses(null)); | |||
} | |||
|
|||
@Test | |||
public void test_getShortCanonicalName_MalformedInput() { | |||
String input = "[String"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inline all of these local variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback, Gary!
I’ve removed the unrelated countUpperCaseLetters code and inlined the local variables in the test as requested.
Let me know if anything else needs to be updated.
a5e5ac4
to
91368c0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @abhisripathi
Thank you for your update. Please see my comments.
className = reverseAbbreviationMap.get(className.substring(0, 1)); | ||
} | ||
// else if (!className.isEmpty()) { | ||
// className = reverseAbbreviationMap.get(className.substring(0, 1)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this comment needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point — I’ve removed the commented-out code to keep the file clean. It was left in unintentionally during the initial iteration. Thanks for pointing it out!
@@ -937,7 +937,7 @@ public void testIsAllUpperCase() { | |||
assertFalse(StringUtils.isAllUpperCase("A1C")); | |||
assertFalse(StringUtils.isAllUpperCase("A/C")); | |||
} | |||
|
|||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Run mvn
by itself to catch all build errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ran mvn clean install and the full build passed successfully.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not the same as running 'mvn' solo which runs the default Maven goal which contains all build checks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ran mvn standalone — all build checks, Javadoc, RAT, and Checkstyle passed with BUILD SUCCESS.
Let me know if anything else is needed!
// else if (!className.isEmpty()) { | ||
// className = reverseAbbreviationMap.get(className.substring(0, 1)); | ||
// } | ||
else if (!className.isEmpty() && className.length() == 1 && reverseAbbreviationMap.containsKey(className)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the length is 1, then it can't be empty, or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right — since className.length() == 1, the string can’t be empty.
I'm planning to remove the !className.isEmpty() check and keep just className.length() == 1 — does that sound good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right — since className.length() == 1, the string can’t be empty. I'm planning to remove the !className.isEmpty() check and keep just className.length() == 1 — does that sound good?
You tell me! ;-) Does this change need more test coverage? Are all formats for canonical names tested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the test suite and found that most formats (primitive, object arrays, inner classes, null) are already covered. I’ve added tests specifically for the malformed descriptors [String and [LString which are directly related to this fix.
91368c0
to
b051362
Compare
b051362
to
4c7ecd9
Compare
-1: Hm, now that I look at this PR again, this test doesn't make sense:
We would be inventing a new syntax for array type names. I don't see where the JRE, Java Language Specification, or Java VM Specification allows such a syntax. The legal values are:
For example: assertEquals("String[]", ClassUtils.getShortCanonicalName(String[].class.getName()));
assertEquals("String[]", ClassUtils.getShortCanonicalName(String[].class.getCanonicalName()));
assertEquals("String[]", ClassUtils.getShortCanonicalName("String[]")); |
Hi Gary, |
Hello @abhisripathi I don't think we should return bad output for bad input; bad input should throw an IAE. Unfortunately, the current implementation is inconsistent, sometimes it throws an exception, sometimes it's GIGO. For example (now in git master): // Note that we throw RuntimeException (but not which one) for the following bad inputs:
assertThrows(RuntimeException.class, () -> ClassUtils.getShortCanonicalName(""));
assertThrows(RuntimeException.class, () -> ClassUtils.getShortCanonicalName("["));
assertThrows(RuntimeException.class, () -> ClassUtils.getShortCanonicalName("[]"));
assertThrows(RuntimeException.class, () -> ClassUtils.getShortCanonicalName("[;"));
assertThrows(RuntimeException.class, () -> ClassUtils.getShortCanonicalName("[];"));
assertThrows(RuntimeException.class, () -> ClassUtils.getShortCanonicalName(" "));
assertThrows(RuntimeException.class, () -> ClassUtils.getShortCanonicalName("[$"));
assertThrows(RuntimeException.class, () -> ClassUtils.getShortCanonicalName("[$a")); We should probably re-implement the heart of this parsing using regular expressions instead of custom code. |
Thanks for the clarification, Gary — that makes sense. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abhisripathi
It looks to me like your assumptions on legal inputs are incorrect. Please see my comments.
@@ -237,6 +237,15 @@ public void test_getAllSuperclasses_Class() { | |||
assertNull(ClassUtils.getAllSuperclasses(null)); | |||
} | |||
|
|||
@Test | |||
public void test_getShortCanonicalName_MalformedInput() { | |||
assertEquals("String[]", ClassUtils.getShortCanonicalName("[String")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not see how "[String"
is a legal name, but "[Ljava.lang.String;"
is legal. Do you read the JLS to allow this? For an unpackaged class Foo
, an array would be "[LFoo;"
.
} | ||
@Test | ||
public void test_getShortCanonicalName_MissingSemicolon() { | ||
assertEquals("String[]", ClassUtils.getShortCanonicalName("[LString")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not see how "[LString"
is a legal name, but "[Ljava.lang.String;"
is legal. Do you read the JLS to allow this? For an unpackaged class Foo
, an array would be "[LFoo;"
.
Closing in favor of #1437 |
This PR fixes issue LANG-1771 where the input "[String" was incorrectly interpreted as "short[]" by the getShortCanonicalName method in ClassUtils.
✅ Root Cause:
The internal descriptor parser treated the first character 'S' from "String" as a JVM type abbreviation for short.
✅ Fix:
✅ Tests:
Closes: https://issues.apache.org/jira/browse/LANG-1771