RFR: 8298065: Provide more information in message of NoSuchFieldError [v4]

Ioi Lam iklam at openjdk.org
Thu Dec 22 23:09:50 UTC 2022


On Thu, 22 Dec 2022 19:35:22 GMT, Matias Saavedra Silva <matsaave at openjdk.org> wrote:

>> 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.
>
> Matias Saavedra Silva has updated the pull request incrementally with one additional commit since the last revision:
> 
>   Improved style and cleanup

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

-------------

PR: https://git.openjdk.org/jdk/pull/11745


More information about the hotspot-runtime-dev mailing list