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
8298065: Provide more information in message of NoSuchFieldError #11745
Conversation
👋 Welcome back matsaave! A progress list of the required criteria for merging this PR into |
@matias9927 The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
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.
Unclear on the way Object types are reported but the general idea is fine.
A few comments/suggestions around the test.
I would also search for any tests that catch NoSuchFieldError
in case they look for specific messages (e.g. in reflection tests).
Thanks.
* questions. | ||
*/ | ||
|
||
/* Let jtreg compile this one with NoSuchFieldTest.java */ |
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.
Suggestion:
/* Base definition for compilation by javac. */
/* Then have a @compile this version into FieldName1.jasm | ||
public class FieldName { | ||
public static long x = 123; | ||
} | ||
*/ | ||
|
||
/* Then have a @compile this version into FieldName2.jasm | ||
public class FieldName { | ||
} | ||
*/ |
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.
These comments belong in the respective jasm files
@@ -0,0 +1,22 @@ | |||
|
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.
Missing copyright and licence header
@@ -0,0 +1,13 @@ | |||
|
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.
Missing copyright and licence header
We shouldn't rebuild the test three times, replacing the binaries of a classfile each time. This makes it difficult to run the test stand-alone. Instead, the test should be written with three separate .jasm files like this:
and the main test java file can do this:
This way, you can just build the test once. After the test finishes, you can rerun the manually by using the .class files from jtreg's output directory. |
NoSuchFieldA won't work because it's a different class. |
* questions. | ||
*/ | ||
|
||
super public class FieldName |
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 should put the Java code that produced this jasm here as a comment.
Object z = FieldName.z; | ||
s = s + "\nz = " + z; | ||
throwTestException("Did not throw NoSuchFieldError", s); | ||
} catch (NoSuchFieldError nsfe) { |
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 could put each field access in a try block and remove all the fields in the second jasm file, so you can test for all the messages specifically. Although the pattern compiler is cool.
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.
Compiling each jasm file after the main file, IS the easiest way to run it outside jtreg.
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.
Here we have very simple conditions to test, so there's no need for NoSuchFieldOutputTest.java to access FieldName.java at all. That's why I propose that all the error conditions should be implemented inside the jasm files alone. NoSuchFieldOutputTest.java can just refer to the error class by name.
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.
Okay so rather than generate the NSFE by separate compilation, Ioi is suggesting the jasm code can reference a non-existent field directly and so throw NSFE. That works too and the test can be re-run stand-alone.
I think we should change the wording of the error message. There was a reason (or maybe it's by accident) that the original message was vague. It just mentioned the name of the field without the name of the class used by the FieldRef. Consider the following example:
If it's rewritten to remove the
The current output is:
After this PR, if we output this:
I think this would be misleading. Top.java was written with the knowledge that "bar" is a field inherited from the "Base" class (**). During execution, "Base" is replaced by an incompatible version that no longer has such a field. However, the error message blames the "Top" class, even when the problem is in the "Base" class. It would be more correct to say this:
** Note: javac generates the FieldRef as "Top.bar" so that the code will work as long as "bar" is declared in "Top" or any of its super types. It doesn't mandate which class must declare this field. This allows future refactoring of the class hierarchy. For example, the "Base" class could be refactored to move all of its declared fields to a super type of "Base". |
for (int j = 0; j < i; j++) { | ||
strncat(buf, "[]", strlen(buf)); | ||
buf[strlen(buf)] = '\0'; | ||
} |
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.
Java array dimensions can be up to 255, so the buf
is not long enough.
Anyway, I think we already have code that can translate a signature like [I
to the source code form of int []
. Try this:
super class Foofoo
version 63:0
{
public static Method main:"([Ljava/lang/String;)V"
stack 30 locals 3
{
iconst_0;
iconst_0;
aconst_null;
invokestatic Method "xxx":"(II[I)V";
return;
}
}
$ java -cp . Foofoo
Exception in thread "main" java.lang.NoSuchMethodError: 'void Foofoo.xxx(int, int, int[])'
at Foofoo.main(Foofoo.jasm)
Writing such conversion is very difficult. You should try to use the existing code (refactoring it as necessary).
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.
After a while of searching I was unable to find anything that could easily provide this type of information. The closest example is Signature::print_as_signature_external_return_type, but this does not work with fields.
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.
Ignore my above comment, I found a way to rework a method in symbol.cpp to accomplish the same task more safely. Thank you for this!
I didn't read "have field bar" as meaning has the direct member field bar, but if you are concerned about that then a simple change is: "Class Top does not have, or inherit, field 'int bar'" or more succinctly: "Class Top does not have a member field 'int bar'" |
|
||
public static final InnerClass Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles; | ||
|
||
} // end Class NoSuchFieldPrimitive |
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.
These jasm test cases are too complicated and contain a lot of details unrelated to what you're testing. They should be simplified as:
/*
public class NoSuchFieldPrimitive {
// static int x;
public NoSuchFieldPrimitive() {
x = 123;
}
}
*/
super public class NoSuchFieldPrimitive
version 65:0
{
// REMOVED static Field x:I;
public Method "<init>":"()V"
stack 2 locals 2
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
bipush 123;
putstatic Field x:"I";
return;
}
} | ||
} | ||
} | ||
|
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.
The SignatureStream for field refs should have exactly one element, and must not be a return type, so this function should be tightened (so it will assert on inputs such as (I)V
or ()L/java/lang.Object;
,
assert(!Signature::is_method(this), "must be a field signature");
SignatureStream ss(this, false);
assert(!ss.is_done(), "must have at least one element in field ref");
assert(!at_return_type(), "field ref can not be a return type");
if (ss.is_array()) {
print_array(os, ss);
} else if (ss.is_reference()) {
print_class(os, ss);
} else {
os->print("%s", type2name(ss.type()));
}
#ifdef ASSERT
ss.next();
assert(ss.is_done(), "must have at most one element in field ref");
#endif
String output = nsfe.getMessage(); | ||
Matcher noSuchFieldMatcher = noSuchFieldPattern.matcher(output); | ||
if (noSuchFieldMatcher.matches()) { | ||
switch(testType) { |
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.
switch(testType) { | |
switch (testType) { |
src/hotspot/share/oops/symbol.hpp
Outdated
void print_as_signature_external_parameters(outputStream *os); | ||
void print_signature_as_external_field_type(outputStream *os); |
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.
Should this be print_as_signature_external_field_type
?
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 think Matias's version is more grammatically correct. Maybe we should rename the two existing functions
symbol.hpp: void print_as_signature_external_return_type(outputStream *os);
symbol.hpp: void print_as_signature_external_parameters(outputStream *os);
to print_signature_as_xxx
.
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.
These are methods on VMSymbol that print the VMSymbol as a signature, so the naming is correct.
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.
Sorry I am a bit obsessed with names.
The two existing methods are used only for method signatures. They each print a portion of the signature. For example, when given a signature like "(ZI)J":
- print_as_signature_external_parameters() prints
boolean, int
. Note that the parentheses aren't printed - print_as_signature_external_return_type() prints
long
Matias's new function is used only for field signatures. David's suggestion of "print_as_signature_external_field_type" is not consistent with the existing names (which do not say "method").
While "print_as_signature_external_parameters()" can be read as "print the parameters of a (method) signature in external format", I think it's clumsy and incomplete. How about:
- print_method_signature_parameters()
- print_method_signature_return_type()
- print_field_signature_type()
I think the word "external" is confusing and can be skipped. For method signatures, we don't need methods that just print the parameters in their internal format (i.e., ZI
or L
), so there's no need to distinguish these functions from "internal" ones.
Also, the first two methods should assert Signature::is_method(this)
and the last one should assert !Signature::is_method(this)
.
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 also get somewhat obsessed with names :)
We are dealing with a VMSymbol which is really just a character string that could represent many things. One of those things is a method signature. So as_signature
is telling us that we are interpreting/expecting the string to be a method signature. We then want either the parameters or return type of the method signature, and those could be in internal or external format. So IMO all the existing naming is perfectly good and desirable.
The issue with the new method is that fields don't have signatures** in the general sense that methods do. A field just has a name and a type. So to me the correct naming here would be as_field_external_type
.
** In the VM the classfile Signature attribute does exist for a FieldInfo but it is only used for fields declared with type variables or parameterized types. Within the VM all fields have their erased type.
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.
as_field_external_type
sounds good to me.
I still think the asserts for Signature::is_method should be added to these 3 functions.
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.
Nothing further from me. Looks good. Thanks.
@matias9927 This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 186 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@dholmes-ora, @iklam, @coleenp) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
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 think it looks great!
Thank you for the comments and corrections @iklam, @dholmes-ora, @coleenp, and @turbanoff ! |
@matias9927 |
/sponsor |
Going to push as commit 43847c4.
Your commit was automatically rebased without conflicts. |
@coleenp @matias9927 Pushed as commit 43847c4. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
A java.lang.NoSuchFieldError is typically thrown when you remove a field but do not recompile the client code that calls the field. However, the message does not indicate in which class the field was not found.
Additionally, java.lang.NoSuchFieldError is thrown if the field is still present but the types are incompatible. For example, if a field is first defined as int, and later changed to long without recompiling the client. The error text has been expanded to include the class name and field type. Verified with tier 1-4 tests.
Old output:
Exception in thread "main" java.lang.NoSuchFieldError: x at NoSuchFieldMain.main(NoSuchFieldMain.java:3)
Example output:
Exception in thread "main" java.lang.NoSuchFieldError: Class Other does not have field 'int x' at NoSuchFieldMain.main(NoSuchFieldMain.java:3)
The added test considers different types of fields like primitives, arrays, and references.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/11745/head:pull/11745
$ git checkout pull/11745
Update a local copy of the PR:
$ git checkout pull/11745
$ git pull https://git.openjdk.org/jdk pull/11745/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 11745
View PR using the GUI difftool:
$ git pr show -t 11745
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/11745.diff