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
8 changes: 6 additions & 2 deletions src/hotspot/share/interpreter/linkResolver.cpp
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -980,7 +980,11 @@ void LinkResolver::resolve_field(fieldDescriptor& fd,
// check if field exists; i.e., if a klass containing the field def has been selected
if (sel_klass == NULL) {
ResourceMark rm(THREAD);
THROW_MSG(vmSymbols::java_lang_NoSuchFieldError(), field->as_C_string());
stringStream ss;
ss.print("Class %s does not have member field '", resolved_klass->external_name());
sig->print_signature_as_external_field_type(&ss);
ss.print(" %s'", field->as_C_string());
THROW_MSG(vmSymbols::java_lang_NoSuchFieldError(), ss.as_string());
}

// Access checking may be turned off when calling from within the VM.
Expand Down
20 changes: 19 additions & 1 deletion src/hotspot/share/oops/symbol.cpp
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -294,6 +294,24 @@ void Symbol::print_as_signature_external_parameters(outputStream *os) {
}
}

void Symbol::print_signature_as_external_field_type(outputStream *os) {
SignatureStream ss(this, false);
assert(!ss.is_done(), "must have at least one element in field ref");
assert(!ss.at_return_type(), "field ref cannot 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
}

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

// Increment refcount while checking for zero. If the Symbol's refcount becomes zero
// a thread could be concurrently removing the Symbol. This is used during SymbolTable
// lookup to avoid reviving a dead Symbol.
Expand Down
3 changes: 2 additions & 1 deletion src/hotspot/share/oops/symbol.hpp
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -275,6 +275,7 @@ class Symbol : public MetaspaceObj {
// separated by ', ' to the outputStream. Prints external names as
// 'double' or 'java.lang.Object[][]'.
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.


void metaspace_pointers_do(MetaspaceClosure* it);
MetaspaceObj::Type type() const { return SymbolType; }
Expand Down
49 changes: 49 additions & 0 deletions test/hotspot/jtreg/runtime/linkResolver/NoSuchFieldArray.jasm
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
public class NoSuchFieldArray {
static char[] z;
public NoSuchFieldArray() {
z = new char[1];
}
}
*/

super public class NoSuchFieldArray
version 65:0
{
// REMOVED static Field z:"[C";

public Method "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
iconst_1;
newarray char;
putstatic Field z:"[C";
return;
}

} // end Class NoSuchFieldArray
50 changes: 50 additions & 0 deletions test/hotspot/jtreg/runtime/linkResolver/NoSuchFieldMultiArray.jasm
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
public class NoSuchFieldMultiArray {
static double[][] a;
public NoSuchFieldMultiArray() {
a = new double[1][1];
}
}
*/

super public class NoSuchFieldMultiArray
version 65:0
{
// REMOVED static Field a:"[[D";

public Method "<init>":"()V"
stack 2 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
iconst_1;
iconst_1;
multianewarray class "[[D", 2;
putstatic Field a:"[[D";
return;
}

} // end Class NoSuchFieldMultiArray
108 changes: 108 additions & 0 deletions test/hotspot/jtreg/runtime/linkResolver/NoSuchFieldOutputTest.java
@@ -0,0 +1,108 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
* @test
* @bug 8298065
* @summary Test output of NoSuchFieldError when field signature does not match
* @compile NoSuchFieldPrimitive.jasm NoSuchFieldReference.jasm
* @compile NoSuchFieldArray.jasm NoSuchFieldMultiArray.jasm
* @run main NoSuchFieldOutputTest
*/

import java.util.regex.Matcher;
import java.util.regex.Pattern;

// Tests the output text of NoSuchFieldError
public class NoSuchFieldOutputTest {

public static void main(java.lang.String[] unused) throws Exception {
try {
Class.forName("NoSuchFieldPrimitive").newInstance();
} catch (NoSuchFieldError nsfe) {
testNoSuchFieldOutput(nsfe, "primitive");
}
try {
Class.forName("NoSuchFieldReference").newInstance();
} 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.

testNoSuchFieldOutput(nsfe, "reference");
}
try {
Class.forName("NoSuchFieldArray").newInstance();
} catch (NoSuchFieldError nsfe) {
testNoSuchFieldOutput(nsfe, "array");
}
try {
Class.forName("NoSuchFieldMultiArray").newInstance();
} catch (NoSuchFieldError nsfe) {
testNoSuchFieldOutput(nsfe, "multiArray");
}
}

private static void testNoSuchFieldOutput(NoSuchFieldError nsfe, String testType) throws Exception {
Pattern noSuchFieldPattern = Pattern.compile("Class (?<classname>[\\w\\d]+) does not have member field '(?<signature>[\\S]+) (?<varname>[\\w\\d]+)'");
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) {

case "primitive":
checkOutputGroups(noSuchFieldMatcher, output, "NoSuchFieldPrimitive", "int", "x");
break;
case "reference":
checkOutputGroups(noSuchFieldMatcher, output, "NoSuchFieldReference", "TestClass", "y");
break;
case "array":
checkOutputGroups(noSuchFieldMatcher, output, "NoSuchFieldArray", "char[]", "z");
break;
case "multiArray":
checkOutputGroups(noSuchFieldMatcher, output, "NoSuchFieldMultiArray", "double[][]", "a");
break;
default:
throwTestException("No matching test", output);
}
} else {
throwTestException("Output format does not match", output);
}
System.out.println(output);
}

private static void checkOutputGroups(Matcher noSuchFieldMatcher, String output,
String testClass, String testSig, String testVar) throws Exception {
String classname = noSuchFieldMatcher.group("classname");
String signature = noSuchFieldMatcher.group("signature");
String varname = noSuchFieldMatcher.group("varname");
if (!classname.equals(testClass)) {
throwTestException("Failed to match class name", output);
}
if (!signature.equals(testSig)) {
throwTestException("Failed to match type signature", output);
}
if (!varname.equals(testVar)) {
throwTestException("Failed to match field name", output);
}
}

private static void throwTestException(String reason, String output) throws Exception {
throw new Exception(reason + " . Stdout is :\n" + output);
}
}
48 changes: 48 additions & 0 deletions test/hotspot/jtreg/runtime/linkResolver/NoSuchFieldPrimitive.jasm
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
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 1 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
bipush 123;
putstatic Field x:"I";
return;
}

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

50 changes: 50 additions & 0 deletions test/hotspot/jtreg/runtime/linkResolver/NoSuchFieldReference.jasm
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

/*
public class NoSuchFieldReference {
static TestClass y;
public NoSuchFieldReference() {
y = new TestClass();
}
}
*/

super public class NoSuchFieldReference
version 65:0
{
// REMOVED static Field y:"LTestClass;";

public Method "<init>":"()V"
stack 2 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
new class TestClass;
dup;
invokespecial Method TestClass."<init>":"()V";
putstatic Field y:"LTestClass;";
return;
}

} // end Class NoSuchFieldReference