Skip to content
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

Closed
wants to merge 13 commits into from

Conversation

matias9927
Copy link
Contributor

@matias9927 matias9927 commented Dec 20, 2022

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

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8298065: Provide more information in message of NoSuchFieldError

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

@bridgekeeper
Copy link

bridgekeeper bot commented Dec 20, 2022

👋 Welcome back matsaave! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk openjdk bot added the rfr Pull request is ready for review label Dec 20, 2022
@openjdk
Copy link

openjdk bot commented Dec 20, 2022

@matias9927 The following label will be automatically applied to this pull request:

  • hotspot-runtime

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.

@openjdk openjdk bot added the hotspot-runtime hotspot-runtime-dev@openjdk.org label Dec 20, 2022
@mlbridge
Copy link

mlbridge bot commented Dec 20, 2022

Copy link
Member

@dholmes-ora dholmes-ora left a 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 */
Copy link
Member

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. */

Comment on lines 29 to 38
/* 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 {
}
*/
Copy link
Member

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 @@

Copy link
Member

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 @@

Copy link
Member

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

src/hotspot/share/interpreter/linkResolver.cpp Outdated Show resolved Hide resolved
@iklam
Copy link
Member

iklam commented Dec 22, 2022

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:

super public class NoSuchFieldA
	version 63:0
{
  // REMOVED static Field staticFieldA:I;

  public Method "<init>":"()V"
	stack 1 locals 1
  {
		aload_0;
		invokespecial	Method java/lang/Object."<init>":"()V";
                iconst_0;
                putstatic	Field staticField:"I"; // This will throw NoSuchFieldError
		return;
  }
}

and the main test java file can do this:

    try {
        Class.forName("NoSuchFieldA").newInstance();
    } catch (NoSuchFieldError e) {
        ....
    }

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.

@coleenp
Copy link
Contributor

coleenp commented Dec 22, 2022

NoSuchFieldA won't work because it's a different class.

* questions.
*/

super public class FieldName
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member

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.

Copy link
Member

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.

@iklam
Copy link
Member

iklam commented Dec 22, 2022

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:

class Base {
  int foo, bar;
}

class Top extends Base {
  public static void main(String args[]) {
    Top t = new Top();
    t.foo = 1;
    t.bar = 1;
  }
}

If it's rewritten to remove the bar field

super class Base
	version 63:0
{
  Field foo:I;
  //Field bar:I;

  Method "<init>":"()V"
	stack 1 locals 1
  {
		aload_0;
		invokespecial	Method java/lang/Object."<init>":"()V";
		return;
  }
}

super class Top extends Base
	version 63:0
{
  Method "<init>":"()V"
	stack 1 locals 1
  {
		aload_0;
		invokespecial	Method Base."<init>":"()V";
		return;
  }
  public static Method main:"([Ljava/lang/String;)V"
	stack 2 locals 2
  {
		new	class Top;
		dup;
		invokespecial	Method "<init>":"()V";
		astore_1;
		aload_1;
		iconst_1;
		putfield	Field Top.foo:"I";
		aload_1;
		iconst_1;
		putfield	Field Top.bar:"I";  // Javac outputs "Top" as the class, not "Base"
		return;
  }
} 

The current output is:

$ java -cp . Top
Exception in thread "main" java.lang.NoSuchFieldError: bar
	at Top.main(top.jasm)

After this PR, if we output this:

$ java -cp . Top
Exception in thread "main" java.lang.NoSuchFieldError: Class Top does not have field 'int bar'
	at Top.main(top.jasm)

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:

NoSuchFieldError: field 'int bar' cannot be be found in class Top or any of its super types

** 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';
}
Copy link
Member

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).

Copy link
Contributor Author

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.

Copy link
Contributor Author

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!

@dholmes-ora
Copy link
Member

dholmes-ora commented Dec 23, 2022

After this PR, if we output this:

$ java -cp . Top
Exception in thread "main" java.lang.NoSuchFieldError: Class Top does not have field 'int bar'
	at Top.main(top.jasm)

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.

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
Copy link
Member

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;
  }

}
}
}

Copy link
Member

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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
switch(testType) {
switch (testType) {

Comment on lines 277 to 278
void print_as_signature_external_parameters(outputStream *os);
void print_signature_as_external_field_type(outputStream *os);
Copy link
Member

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?

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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) .

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

@dholmes-ora dholmes-ora left a 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.

@openjdk
Copy link

openjdk bot commented Jan 10, 2023

@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:

8298065: Provide more information in message of NoSuchFieldError

Reviewed-by: dholmes, iklam, coleenp

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 master branch:

  • a17b563: 8299918: Update Xcode11.3.1-MacOSX10.15 devkit at Oracle
  • 9c1e98d: 8298321: 2 File Leak defect groups in 2 files
  • d663b5d: 8299498: Usage of constructors of primitive wrapper classes should be avoided in java.lang API docs
  • 437d69a: 8299836: Make user.timezone system property searchable
  • c7716a0: 8299571: ZoneRulesProvider.registerProvider() can leave inconsistent state on failure
  • 4cd87f1: 8299835: (jrtfs) Unnecessary null check in JrtPath.getAttributes
  • 7d3400b: 8299864: ZipFileStore#supportsFileAttributeView(String) doesn't throw NPE
  • d15285f: 8299956: [BACKOUT] 8297487: G1 Remark: no need to keep alive oop constants of nmethods on stack
  • 5a9490a: 8299853: Serial: Use more concrete type for TenuredGeneration::_the_space
  • f857f8a: 8299327: Allow super late barrier expansion of store barriers in C2
  • ... and 176 more: https://git.openjdk.org/jdk/compare/386db07143883f85307138eca2f0305d997a2171...master

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 /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Jan 10, 2023
Copy link
Contributor

@coleenp coleenp left a 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!

@matias9927
Copy link
Contributor Author

Thank you for the comments and corrections @iklam, @dholmes-ora, @coleenp, and @turbanoff !
/integrate

@openjdk openjdk bot added the sponsor Pull request is ready to be sponsored label Jan 11, 2023
@openjdk
Copy link

openjdk bot commented Jan 11, 2023

@matias9927
Your change (at version 3c07b81) is now ready to be sponsored by a Committer.

@coleenp
Copy link
Contributor

coleenp commented Jan 11, 2023

/sponsor

@openjdk
Copy link

openjdk bot commented Jan 11, 2023

Going to push as commit 43847c4.
Since your change was applied there have been 186 commits pushed to the master branch:

  • a17b563: 8299918: Update Xcode11.3.1-MacOSX10.15 devkit at Oracle
  • 9c1e98d: 8298321: 2 File Leak defect groups in 2 files
  • d663b5d: 8299498: Usage of constructors of primitive wrapper classes should be avoided in java.lang API docs
  • 437d69a: 8299836: Make user.timezone system property searchable
  • c7716a0: 8299571: ZoneRulesProvider.registerProvider() can leave inconsistent state on failure
  • 4cd87f1: 8299835: (jrtfs) Unnecessary null check in JrtPath.getAttributes
  • 7d3400b: 8299864: ZipFileStore#supportsFileAttributeView(String) doesn't throw NPE
  • d15285f: 8299956: [BACKOUT] 8297487: G1 Remark: no need to keep alive oop constants of nmethods on stack
  • 5a9490a: 8299853: Serial: Use more concrete type for TenuredGeneration::_the_space
  • f857f8a: 8299327: Allow super late barrier expansion of store barriers in C2
  • ... and 176 more: https://git.openjdk.org/jdk/compare/386db07143883f85307138eca2f0305d997a2171...master

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Jan 11, 2023
@openjdk openjdk bot closed this Jan 11, 2023
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review sponsor Pull request is ready to be sponsored labels Jan 11, 2023
@openjdk
Copy link

openjdk bot commented Jan 11, 2023

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hotspot-runtime hotspot-runtime-dev@openjdk.org integrated Pull request has been integrated
5 participants