8169559: Add class loader names to relevant VM messages

Added new method class_in_module_of_loader to provide a standard format for class information within error messages.

Reviewed-by: goetz, hseigel, mchung
This commit is contained in:
Lois Foltan 2018-06-25 11:33:11 -04:00
parent fc73803071
commit 7ca4027957
13 changed files with 379 additions and 75 deletions
src/hotspot/share
test/hotspot/jtreg
runtime
vmTestbase/jit/t/t113

@ -955,9 +955,10 @@ void ClassFileParser::parse_interfaces(const ClassFileStream* const stream,
if (!interf->is_interface()) {
THROW_MSG(vmSymbols::java_lang_IncompatibleClassChangeError(),
err_msg("Class %s can not implement %s, because it is not an interface",
err_msg("class %s can not implement %s, because it is not an interface (%s)",
_class_name->as_klass_external_name(),
interf->class_loader_and_module_name()));
interf->external_name(),
interf->class_in_module_of_loader()));
}
if (InstanceKlass::cast(interf)->has_nonstatic_concrete_methods()) {

@ -40,10 +40,12 @@
#include "jfr/support/jfrTraceIdExtension.hpp"
#endif
#define UNNAMED_MODULE "Unnamed Module"
#define UNNAMED_MODULE "unnamed module"
#define UNNAMED_MODULE_LEN 14
#define JAVAPKG "java"
#define JAVAPKG_LEN 4
#define JAVA_BASE_NAME "java.base"
#define JAVA_BASE_NAME_LEN 9
class ModuleClosure;

@ -592,14 +592,18 @@ void LinkResolver::check_method_accessability(Klass* ref_klass,
// from nest-host resolution, have been allowed to propagate.
if (!can_access) {
ResourceMark rm(THREAD);
bool same_module = (sel_klass->module() == ref_klass->module());
Exceptions::fthrow(
THREAD_AND_LOCATION,
vmSymbols::java_lang_IllegalAccessError(),
"tried to access method %s.%s%s from class %s",
"class %s tried to access method %s.%s%s (%s%s%s)",
ref_klass->external_name(),
sel_klass->external_name(),
sel_method->name()->as_C_string(),
sel_method->signature()->as_C_string(),
ref_klass->external_name()
(same_module) ? ref_klass->joint_in_module_of_loader(sel_klass) : ref_klass->class_in_module_of_loader(),
(same_module) ? "" : "; ",
(same_module) ? "" : sel_klass->class_in_module_of_loader()
);
return;
}

@ -2850,9 +2850,14 @@ Method* InstanceKlass::method_at_itable(Klass* holder, int index, TRAPS) {
if (cnt >= nof_interfaces) {
ResourceMark rm(THREAD);
stringStream ss;
bool same_module = (module() == holder->module());
ss.print("Receiver class %s does not implement "
"the interface %s defining the method to be called",
class_loader_and_module_name(), holder->class_loader_and_module_name());
"the interface %s defining the method to be called "
"(%s%s%s)",
external_name(), holder->external_name(),
(same_module) ? joint_in_module_of_loader(holder) : class_in_module_of_loader(),
(same_module) ? "" : "; ",
(same_module) ? "" : holder->class_in_module_of_loader());
THROW_MSG_NULL(vmSymbols::java_lang_IncompatibleClassChangeError(), ss.as_string());
}

@ -774,80 +774,124 @@ bool Klass::verify_itable_index(int i) {
#endif // PRODUCT
// The caller of class_loader_and_module_name() (or one of its callers)
// must use a ResourceMark in order to correctly free the result.
const char* Klass::class_loader_and_module_name() const {
const char* delim = "/";
size_t delim_len = strlen(delim);
// Caller needs ResourceMark
// joint_in_module_of_loader provides an optimization if 2 classes are in
// the same module to succinctly print out relevant information about their
// module name and class loader's name_and_id for error messages.
// Format:
// <fully-qualified-external-class-name1> and <fully-qualified-external-class-name2>
// are in module <module-name>[@<version>]
// of loader <loader-name_and_id>[, parent loader <parent-loader-name_and_id>]
const char* Klass::joint_in_module_of_loader(const Klass* class2, bool include_parent_loader) const {
assert(module() == class2->module(), "classes do not have the same module");
const char* class1_name = external_name();
size_t len = strlen(class1_name) + 1;
const char* fqn = external_name();
// Length of message to return; always include FQN
size_t msglen = strlen(fqn) + 1;
const char* class2_description = class2->class_in_module_of_loader(true, include_parent_loader);
len += strlen(class2_description);
bool has_cl_name = false;
bool has_mod_name = false;
bool has_version = false;
len += strlen(" and ");
// Use class loader name, if exists and not builtin
const char* class_loader_name = "";
ClassLoaderData* cld = class_loader_data();
assert(cld != NULL, "class_loader_data should not be NULL");
if (!cld->is_builtin_class_loader_data()) {
// If not builtin, look for name
oop loader = class_loader();
if (loader != NULL) {
oop class_loader_name_oop = java_lang_ClassLoader::name(loader);
if (class_loader_name_oop != NULL) {
class_loader_name = java_lang_String::as_utf8_string(class_loader_name_oop);
if (class_loader_name != NULL && class_loader_name[0] != '\0') {
has_cl_name = true;
msglen += strlen(class_loader_name) + delim_len;
}
}
}
char* joint_description = NEW_RESOURCE_ARRAY_RETURN_NULL(char, len);
// Just return the FQN if error when allocating string
if (joint_description == NULL) {
return class1_name;
}
jio_snprintf(joint_description, len, "%s and %s",
class1_name,
class2_description);
return joint_description;
}
// Caller needs ResourceMark
// class_in_module_of_loader provides a standard way to include
// relevant information about a class, such as its module name as
// well as its class loader's name_and_id, in error messages and logging.
// Format:
// <fully-qualified-external-class-name> is in module <module-name>[@<version>]
// of loader <loader-name_and_id>[, parent loader <parent-loader-name_and_id>]
const char* Klass::class_in_module_of_loader(bool use_are, bool include_parent_loader) const {
// 1. fully qualified external name of class
const char* klass_name = external_name();
size_t len = strlen(klass_name) + 1;
// 2. module name + @version
const char* module_name = "";
const char* version = "";
bool has_version = false;
bool module_is_named = false;
const char* module_name_phrase = "";
const Klass* bottom_klass = is_objArray_klass() ?
ObjArrayKlass::cast(this)->bottom_klass() : this;
ObjArrayKlass::cast(this)->bottom_klass() : this;
if (bottom_klass->is_instance_klass()) {
ModuleEntry* module = InstanceKlass::cast(bottom_klass)->module();
// Use module name, if exists
if (module->is_named()) {
has_mod_name = true;
module_is_named = true;
module_name_phrase = "module ";
module_name = module->name()->as_C_string();
msglen += strlen(module_name);
len += strlen(module_name);
// Use version if exists and is not a jdk module
if (module->should_show_version()) {
has_version = true;
version = module->version()->as_C_string();
msglen += strlen(version) + 1; // +1 for "@"
// Include stlen(version) + 1 for the "@"
len += strlen(version) + 1;
}
} else {
module_name = UNNAMED_MODULE;
len += UNNAMED_MODULE_LEN;
}
} else {
// klass is an array of primitives, so its module is java.base
// klass is an array of primitives, module is java.base
module_is_named = true;
module_name_phrase = "module ";
module_name = JAVA_BASE_NAME;
len += JAVA_BASE_NAME_LEN;
}
if (has_cl_name || has_mod_name) {
msglen += delim_len;
// 3. class loader's name_and_id
ClassLoaderData* cld = class_loader_data();
assert(cld != NULL, "class_loader_data should not be null");
const char* loader_name_and_id = cld->loader_name_and_id();
len += strlen(loader_name_and_id);
// 4. include parent loader information
const char* parent_loader_phrase = "";
const char* parent_loader_name_and_id = "";
if (include_parent_loader &&
!cld->is_builtin_class_loader_data()) {
oop parent_loader = java_lang_ClassLoader::parent(class_loader());
ClassLoaderData *parent_cld = ClassLoaderData::class_loader_data(parent_loader);
assert(parent_cld != NULL, "parent's class loader data should not be null");
parent_loader_name_and_id = parent_cld->loader_name_and_id();
parent_loader_phrase = ", parent loader ";
len += strlen(parent_loader_phrase) + strlen(parent_loader_name_and_id);
}
char* message = NEW_RESOURCE_ARRAY_RETURN_NULL(char, msglen);
// Start to construct final full class description string
len += ((use_are) ? strlen(" are in ") : strlen(" is in "));
len += strlen(module_name_phrase) + strlen(" of loader ");
// Just return the FQN if error in allocating string
if (message == NULL) {
return fqn;
char* class_description = NEW_RESOURCE_ARRAY_RETURN_NULL(char, len);
// Just return the FQN if error when allocating string
if (class_description == NULL) {
return klass_name;
}
jio_snprintf(message, msglen, "%s%s%s%s%s%s%s",
class_loader_name,
(has_cl_name) ? delim : "",
(has_mod_name) ? module_name : "",
jio_snprintf(class_description, len, "%s %s in %s%s%s%s of loader %s%s%s",
klass_name,
(use_are) ? "are" : "is",
module_name_phrase,
module_name,
(has_version) ? "@" : "",
(has_version) ? version : "",
(has_cl_name || has_mod_name) ? delim : "",
fqn);
return message;
loader_name_and_id,
parent_loader_phrase,
parent_loader_name_and_id);
return class_description;
}

@ -560,7 +560,8 @@ protected:
// and the package separators as '/'.
virtual const char* signature_name() const;
const char* class_loader_and_module_name() const;
const char* joint_in_module_of_loader(const Klass* class2, bool include_parent_loader = false) const;
const char* class_in_module_of_loader(bool use_are = false, bool include_parent_loader = false) const;
// Returns "interface", "abstract class" or "class".
const char* external_kind() const;

@ -1959,14 +1959,27 @@ char* SharedRuntime::generate_class_cast_message(
// must use a ResourceMark in order to correctly free the result.
char* SharedRuntime::generate_class_cast_message(
Klass* caster_klass, Klass* target_klass, Symbol* target_klass_name) {
const char* caster_name = caster_klass->class_loader_and_module_name();
const char* caster_name = caster_klass->external_name();
assert(target_klass != NULL || target_klass_name != NULL, "one must be provided");
const char* target_name = target_klass == NULL ? target_klass_name->as_C_string() :
target_klass->class_loader_and_module_name();
target_klass->external_name();
size_t msglen = strlen(caster_name) + strlen(" cannot be cast to ") + strlen(target_name) + 1;
size_t msglen = strlen(caster_name) + strlen("class ") + strlen(" cannot be cast to class ") + strlen(target_name) + 1;
const char* caster_klass_description = "";
const char* target_klass_description = "";
const char* klass_separator = "";
if (target_klass != NULL && caster_klass->module() == target_klass->module()) {
caster_klass_description = caster_klass->joint_in_module_of_loader(target_klass);
} else {
caster_klass_description = caster_klass->class_in_module_of_loader();
target_klass_description = (target_klass != NULL) ? target_klass->class_in_module_of_loader() : "";
klass_separator = (target_klass != NULL) ? "; " : "";
}
// add 3 for parenthesis and preceeding space
msglen += strlen(caster_klass_description) + strlen(target_klass_description) + strlen(klass_separator) + 3;
char* message = NEW_RESOURCE_ARRAY_RETURN_NULL(char, msglen);
if (message == NULL) {
@ -1975,9 +1988,13 @@ char* SharedRuntime::generate_class_cast_message(
} else {
jio_snprintf(message,
msglen,
"%s cannot be cast to %s",
"class %s cannot be cast to class %s (%s%s%s)",
caster_name,
target_name);
target_name,
caster_klass_description,
klass_separator,
target_klass_description
);
}
return message;
}

@ -212,7 +212,7 @@ public class IncompatibleClassChangeErrorTest {
}
private static String expectedErrorMessage3 =
"Class test.ICC3_B can not implement test.ICC3_A, because it is not an interface";
"class test.ICC3_B can not implement test.ICC3_A, because it is not an interface (test.ICC3_A is in unnamed module of loader 'app')";
public static void test3_implementsClass() throws Exception {
try {

@ -0,0 +1,121 @@
/*
* Copyright (c) 2018, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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
* @summary Test that if module m1x can read module m2x, AND package p2 in m2x is
* exported qualifiedly to m1x, then class p1.c1 in m1x can read p2.c2 in m2x.
* However, p1.c1 tries to access a private method within p2.c2, verify
* that the IAE message contains the correct loader and module names.
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @compile p1/c1.jasm
* @compile p2/c2.jasm
* @compile myloaders/MySameClassLoader.java
* @run main/othervm -Xbootclasspath/a:. ExpQualToM1PrivateMethodIAE
*/
import static jdk.test.lib.Asserts.*;
import java.lang.module.Configuration;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import myloaders.MySameClassLoader;
public class ExpQualToM1PrivateMethodIAE {
// Create a layer over the boot layer.
// Define modules within this layer to test access between
// publically defined classes within packages of those modules.
public void createLayerOnBoot() throws Throwable {
// Define module: m1x
// Can read: java.base, m2x
// Packages: p1
// Packages exported: p1 is exported unqualifiedly
ModuleDescriptor descriptor_m1x =
ModuleDescriptor.newModule("m1x")
.requires("java.base")
.requires("m2x")
.exports("p1")
.build();
// Define module: m2x
// Can read: java.base
// Packages: p2
// Packages exported: p2 is exported qualifiedly to m1x
ModuleDescriptor descriptor_m2x =
ModuleDescriptor.newModule("m2x")
.requires("java.base")
.exports("p2", Set.of("m1x"))
.build();
// Set up a ModuleFinder containing all modules for this layer.
ModuleFinder finder = ModuleLibrary.of(descriptor_m1x, descriptor_m2x);
// Resolves "m1x"
Configuration cf = ModuleLayer.boot()
.configuration()
.resolve(finder, ModuleFinder.of(), Set.of("m1x"));
// map each module to the same class loader for this test
Map<String, ClassLoader> map = new HashMap<>();
map.put("m1x", MySameClassLoader.loader1);
map.put("m2x", MySameClassLoader.loader1);
// Create layer that contains m1x & m2x
ModuleLayer layer = ModuleLayer.boot().defineModules(cf, map::get);
assertTrue(layer.findLoader("m1x") == MySameClassLoader.loader1);
assertTrue(layer.findLoader("m2x") == MySameClassLoader.loader1);
// now use the same loader to load class p1.c1
Class p1_c1_class = MySameClassLoader.loader1.loadClass("p1.c1");
try {
p1_c1_class.newInstance();
throw new RuntimeException("Test Failed, an IAE should be thrown since p2/c2's method2 is private");
} catch (IllegalAccessError e) {
String message = e.getMessage();
System.out.println(e.toString());
// java.lang.IllegalAccessError:
// tried to access method p2.c2.method2()V from class p1.c1 (p2.c2 is in module m2x of loader
// myloaders.MySameClassLoader @<id>; p1.c1 is in module m1x of loader myloaders.MySameClassLoader @<id>)
if (!message.contains("class p1.c1 tried to access method p2.c2.method2()V (p1.c1 is in module m1x of loader myloaders.MySameClassLoader @") ||
!message.contains("; p2.c2 is in module m2x of loader myloaders.MySameClassLoader @")) {
throw new RuntimeException("Test Failed, an IAE was thrown with the wrong message: " + e.toString());
}
} catch (Throwable e) {
throw new RuntimeException("Test Failed, an IAE should be thrown since p2/c2's method2 is private");
}
}
public static void main(String args[]) throws Throwable {
ExpQualToM1PrivateMethodIAE test = new ExpQualToM1PrivateMethodIAE();
test.createLayerOnBoot();
}
}

@ -0,0 +1,43 @@
/*
* Copyright (c) 2018, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package p1;
super public class c1 version 55:0 {
public Method "<init>":"()V"
stack 2 locals 2
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
new class p2/c2;
dup;
invokespecial Method p2/c2."<init>":"()V";
astore_1;
aload_1;
invokevirtual Method p2/c2.method2:"()V";
return;
}
} // end Class c1

@ -0,0 +1,42 @@
/*
* Copyright (c) 2018, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
package p2;
super public class c2 version 55:0 {
public Method "<init>":"()V"
stack 1 locals 1
{
aload_0;
invokespecial Method java/lang/Object."<init>":"()V";
return;
}
private Method method2:"()V"
stack 0 locals 1
{
return;
}
} // end Class c2

@ -48,6 +48,7 @@ public class CCE_module_msg {
public static void main(String[] args) throws Throwable {
// Should not display version
invalidObjectToDerived();
invalidOriginalInnerToDerived();
invalidTimeToDerived();
invalidHeadersToDerived();
// Should display version
@ -66,8 +67,25 @@ public class CCE_module_msg {
}
throw new RuntimeException("ClassCastException wasn't thrown, test failed.");
} catch (ClassCastException cce) {
System.out.println(cce.getMessage());
if (!cce.getMessage().contains("java.base/java.lang.Object cannot be cast to Derived")) {
System.out.println(cce.toString());
if (!cce.getMessage().contains("class java.lang.Object cannot be cast to class Derived (java.lang.Object is in module java.base of loader 'bootstrap'; Derived is in unnamed module of loader 'app')")) {
throw new RuntimeException("Wrong message: " + cce.getMessage());
}
}
}
public static void invalidOriginalInnerToDerived() {
OriginalInner instance = new OriginalInner();
int left = 23;
int right = 42;
try {
for (int i = 0; i < 1; i += 1) {
left = ((Derived) (java.lang.Object)instance).method(left, right);
}
throw new RuntimeException("ClassCastException wasn't thrown, test failed.");
} catch (ClassCastException cce) {
System.out.println(cce.toString());
if (!cce.getMessage().contains("class OriginalInner cannot be cast to class Derived (OriginalInner and Derived are in unnamed module of loader 'app')")) {
throw new RuntimeException("Wrong message: " + cce.getMessage());
}
}
@ -84,8 +102,8 @@ public class CCE_module_msg {
}
throw new RuntimeException("ClassCastException wasn't thrown, test failed.");
} catch (ClassCastException cce) {
System.out.println(cce.getMessage());
if (!cce.getMessage().contains("java.sql/java.sql.Time cannot be cast to Derived")) {
System.out.println(cce.toString());
if (!cce.getMessage().contains("class java.sql.Time cannot be cast to class Derived (java.sql.Time is in module java.sql of loader 'platform'; Derived is in unnamed module of loader 'app')")) {
throw new RuntimeException("Wrong message: " + cce.getMessage());
}
}
@ -102,8 +120,8 @@ public class CCE_module_msg {
}
throw new RuntimeException("ClassCastException wasn't thrown, test failed.");
} catch (ClassCastException cce) {
System.out.println(cce.getMessage());
if (!cce.getMessage().contains("jdk.httpserver/com.sun.net.httpserver.Headers cannot be cast to Derived")) {
System.out.println(cce.toString());
if (!cce.getMessage().contains("class com.sun.net.httpserver.Headers cannot be cast to class Derived (com.sun.net.httpserver.Headers is in module jdk.httpserver of loader 'platform'; Derived is in unnamed module of loader 'app')")) {
throw new RuntimeException("Wrong message: " + cce.getMessage());
}
}
@ -132,10 +150,9 @@ public class CCE_module_msg {
throw new RuntimeException("ClassCastException wasn't thrown, test failed.");
} catch (ClassCastException cce) {
String exception = cce.getMessage();
System.out.println(exception);
if (exception.contains("module_two/p2.c2") ||
!(exception.contains("module_two@") &&
exception.contains("/p2.c2 cannot be cast to java.base/java.lang.String"))) {
System.out.println(cce.toString());
if (!exception.contains("class p2.c2 cannot be cast to class java.lang.String (p2.c2 is in module module_two@") ||
!exception.contains(" of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')")) {
throw new RuntimeException("Wrong message: " + exception);
}
}
@ -160,14 +177,21 @@ public class CCE_module_msg {
throw new RuntimeException("ClassCastException wasn't thrown, test failed.");
} catch (ClassCastException cce) {
String exception = cce.getMessage();
System.out.println(exception);
if (!exception.contains("MyClassLoader//p4.c4 cannot be cast to java.base/java.lang.String")) {
System.out.println(cce.toString());
if (!exception.contains("class p4.c4 cannot be cast to class java.lang.String (p4.c4 is in unnamed module of loader 'MyClassLoader' @") ||
!exception.contains("; java.lang.String is in module java.base of loader 'bootstrap')")) {
throw new RuntimeException("Wrong message: " + exception);
}
}
}
}
class OriginalInner extends java.lang.Object {
public int method(int left, int right) {
return right;
}
}
class Derived extends java.lang.Object {
public int method(int left, int right) {
return right;

@ -1,2 +1,2 @@
java.lang.ClassCastException: jit.t.t113.kid1 cannot be cast to jit.t.t113.kid2
java.lang.ClassCastException: class jit.t.t113.kid1 cannot be cast to class jit.t.t113.kid2 (jit.t.t113.kid1 and jit.t.t113.kid2 are in unnamed module of loader 'app')
at jit.t.t113.t113.main(t113.java:59)