Skip to content

Commit 4533109

Browse files
author
David Holmes
committed
8345911: Enhance error message when IncompatibleClassChangeError is thrown for sealed class loading failures
Reviewed-by: coleenp, alanb
1 parent ea50c54 commit 4533109

15 files changed

+201
-43
lines changed

src/hotspot/share/classfile/classFileError.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ void ClassFileParser::classfile_parse_error(const char* msg, TRAPS) const {
4545
msg, _class_name->as_C_string());
4646
}
4747

48+
// The caller is required/expected to have a ResourceMark in this case.
4849
void ClassFileParser::classfile_parse_error(const char* msg,
4950
int index,
5051
TRAPS) const {
5152
assert(_class_name != nullptr, "invariant");
52-
ResourceMark rm(THREAD);
5353
Exceptions::fthrow(THREAD_AND_LOCATION, vmSymbols::java_lang_ClassFormatError(),
5454
msg, index, _class_name->as_C_string());
5555
}
@@ -92,6 +92,12 @@ void ClassFileParser::classfile_icce_error(const char* msg,
9292
msg, _class_name->as_klass_external_name(), k->external_name());
9393
}
9494

95+
void ClassFileParser::classfile_icce_error(const char* msg,
96+
TRAPS) const {
97+
ResourceMark rm(THREAD);
98+
Exceptions::fthrow(THREAD_AND_LOCATION, vmSymbols::java_lang_IncompatibleClassChangeError(), msg);
99+
}
100+
95101
void ClassFileParser::classfile_ucve_error(const char* msg,
96102
const Symbol* class_name,
97103
u2 major,

src/hotspot/share/classfile/classFileParser.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4070,9 +4070,13 @@ void ClassFileParser::check_super_class_access(const InstanceKlass* this_klass,
40704070
return;
40714071
}
40724072

4073-
if (super_ik->is_sealed() && !super_ik->has_as_permitted_subclass(this_klass)) {
4074-
classfile_icce_error("class %s cannot inherit from sealed class %s", super_ik, THREAD);
4075-
return;
4073+
if (super_ik->is_sealed()) {
4074+
stringStream ss;
4075+
ResourceMark rm(THREAD);
4076+
if (!super_ik->has_as_permitted_subclass(this_klass, ss)) {
4077+
classfile_icce_error(ss.as_string(), THREAD);
4078+
return;
4079+
}
40764080
}
40774081

40784082
Reflection::VerifyClassAccessResults vca_result =
@@ -4117,12 +4121,13 @@ void ClassFileParser::check_super_interface_access(const InstanceKlass* this_kla
41174121
InstanceKlass* const k = local_interfaces->at(i);
41184122
assert (k != nullptr && k->is_interface(), "invalid interface");
41194123

4120-
if (k->is_sealed() && !k->has_as_permitted_subclass(this_klass)) {
4121-
classfile_icce_error(this_klass->is_interface() ?
4122-
"class %s cannot extend sealed interface %s" :
4123-
"class %s cannot implement sealed interface %s",
4124-
k, THREAD);
4125-
return;
4124+
if (k->is_sealed()) {
4125+
stringStream ss;
4126+
ResourceMark rm(THREAD);
4127+
if (!k->has_as_permitted_subclass(this_klass, ss)) {
4128+
classfile_icce_error(ss.as_string(), THREAD);
4129+
return;
4130+
}
41264131
}
41274132

41284133
Reflection::VerifyClassAccessResults vca_result =

src/hotspot/share/classfile/classFileParser.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ class ClassFileParser {
367367
const Klass* k,
368368
TRAPS) const;
369369

370+
// Uses msg directly in the ICCE, with no additional content
371+
void classfile_icce_error(const char* msg,
372+
TRAPS) const;
373+
370374
void classfile_ucve_error(const char* msg,
371375
const Symbol* class_name,
372376
u2 major,

src/hotspot/share/classfile/moduleEntry.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ void ModuleEntryTable::modules_do(ModuleClosure* closure) {
742742
void ModuleEntry::print(outputStream* st) {
743743
st->print_cr("entry " PTR_FORMAT " name %s module " PTR_FORMAT " loader %s version %s location %s strict %s",
744744
p2i(this),
745-
name() == nullptr ? UNNAMED_MODULE : name()->as_C_string(),
745+
name_as_C_string(),
746746
p2i(module()),
747747
loader_data()->loader_name_and_id(),
748748
version() != nullptr ? version()->as_C_string() : "nullptr",

src/hotspot/share/classfile/moduleEntry.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ class ModuleEntry : public CHeapObj<mtModule> {
185185
static ModuleEntry* create_boot_unnamed_module(ClassLoaderData* cld);
186186
static ModuleEntry* new_unnamed_module_entry(Handle module_handle, ClassLoaderData* cld);
187187

188+
// Note caller requires ResourceMark
189+
const char* name_as_C_string() {
190+
return is_named() ? name()->as_C_string() : UNNAMED_MODULE;
191+
}
188192
void print(outputStream* st = tty);
189193
void verify();
190194

src/hotspot/share/oops/instanceKlass.cpp

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,31 +212,45 @@ bool InstanceKlass::has_nest_member(JavaThread* current, InstanceKlass* k) const
212212
return false;
213213
}
214214

215-
// Called to verify that k is a permitted subclass of this class
216-
bool InstanceKlass::has_as_permitted_subclass(const InstanceKlass* k) const {
215+
// Called to verify that k is a permitted subclass of this class.
216+
// The incoming stringStream is used to format the messages for error logging and for the caller
217+
// to use for exception throwing.
218+
bool InstanceKlass::has_as_permitted_subclass(const InstanceKlass* k, stringStream& ss) const {
217219
Thread* current = Thread::current();
218220
assert(k != nullptr, "sanity check");
219221
assert(_permitted_subclasses != nullptr && _permitted_subclasses != Universe::the_empty_short_array(),
220222
"unexpected empty _permitted_subclasses array");
221223

222224
if (log_is_enabled(Trace, class, sealed)) {
223225
ResourceMark rm(current);
224-
log_trace(class, sealed)("Checking for permitted subclass of %s in %s",
226+
log_trace(class, sealed)("Checking for permitted subclass %s in %s",
225227
k->external_name(), this->external_name());
226228
}
227229

228230
// Check that the class and its super are in the same module.
229231
if (k->module() != this->module()) {
230-
ResourceMark rm(current);
231-
log_trace(class, sealed)("Check failed for same module of permitted subclass %s and sealed class %s",
232-
k->external_name(), this->external_name());
232+
ss.print("Failed same module check: subclass %s is in module '%s' with loader %s, "
233+
"and sealed class %s is in module '%s' with loader %s",
234+
k->external_name(),
235+
k->module()->name_as_C_string(),
236+
k->module()->loader_data()->loader_name_and_id(),
237+
this->external_name(),
238+
this->module()->name_as_C_string(),
239+
this->module()->loader_data()->loader_name_and_id());
240+
log_trace(class, sealed)(" - %s", ss.as_string());
233241
return false;
234242
}
235243

236244
if (!k->is_public() && !is_same_class_package(k)) {
237-
ResourceMark rm(current);
238-
log_trace(class, sealed)("Check failed, subclass %s not public and not in the same package as sealed class %s",
239-
k->external_name(), this->external_name());
245+
ss.print("Failed same package check: non-public subclass %s is in package '%s' with classloader %s, "
246+
"and sealed class %s is in package '%s' with classloader %s",
247+
k->external_name(),
248+
k->package() != nullptr ? k->package()->name()->as_C_string() : "unnamed",
249+
k->module()->loader_data()->loader_name_and_id(),
250+
this->external_name(),
251+
this->package() != nullptr ? this->package()->name()->as_C_string() : "unnamed",
252+
this->module()->loader_data()->loader_name_and_id());
253+
log_trace(class, sealed)(" - %s", ss.as_string());
240254
return false;
241255
}
242256

@@ -248,7 +262,10 @@ bool InstanceKlass::has_as_permitted_subclass(const InstanceKlass* k) const {
248262
return true;
249263
}
250264
}
251-
log_trace(class, sealed)("- class is NOT a permitted subclass!");
265+
266+
ss.print("Failed listed permitted subclass check: class %s is not a permitted subclass of %s",
267+
k->external_name(), this->external_name());
268+
log_trace(class, sealed)(" - %s", ss.as_string());
252269
return false;
253270
}
254271

src/hotspot/share/oops/instanceKlass.hpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,8 +456,10 @@ class InstanceKlass: public Klass {
456456
// Check if this klass is a nestmate of k - resolves this nest-host and k's
457457
bool has_nestmate_access_to(InstanceKlass* k, TRAPS);
458458

459-
// Called to verify that k is a permitted subclass of this class
460-
bool has_as_permitted_subclass(const InstanceKlass* k) const;
459+
// Called to verify that k is a permitted subclass of this class.
460+
// The incoming stringStream is used for logging, and for the caller to create
461+
// a detailed exception message on failure.
462+
bool has_as_permitted_subclass(const InstanceKlass* k, stringStream& ss) const;
461463

462464
enum InnerClassAttributeOffset {
463465
// From http://mirror.eng/products/jdk/1.1/docs/guide/innerclasses/spec/innerclasses.doc10.html#18814

test/hotspot/jtreg/runtime/modules/SealedInterfaceModuleTest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -74,7 +74,9 @@ public static void main(String args[]) throws Throwable {
7474
Class p2_C2_class = Class.forName("sealedP2.C2");
7575
throw new RuntimeException("Expected IncompatibleClassChangeError exception not thrown");
7676
} catch (IncompatibleClassChangeError e) {
77-
if (!e.getMessage().contains("cannot implement sealed interface")) {
77+
if (!e.getMessage().equals("Failed same package check: non-public subclass sealedP2.C2 " +
78+
"is in package 'sealedP2' with classloader 'app', and sealed class " +
79+
"sealedP1.SuperInterface is in package 'sealedP1' with classloader 'app'")) {
7880
throw new RuntimeException("Wrong IncompatibleClassChangeError exception thrown: " + e.getMessage());
7981
}
8082
}
@@ -86,7 +88,9 @@ public static void main(String args[]) throws Throwable {
8688
Class p3_C3_class = Class.forName("sealedP3.C3");
8789
throw new RuntimeException("Expected IncompatibleClassChangeError exception not thrown");
8890
} catch (IncompatibleClassChangeError e) {
89-
if (!e.getMessage().contains("cannot implement sealed interface")) {
91+
if (!e.getMessage().equals("Failed same module check: subclass sealedP3.C3 is in module 'module_two' " +
92+
"with loader 'app', and sealed class sealedP1.SuperInterface is in module " +
93+
"'module_one' with loader 'app'")) {
9094
throw new RuntimeException("Wrong IncompatibleClassChangeError exception thrown: " + e.getMessage());
9195
}
9296
}

test/hotspot/jtreg/runtime/modules/SealedModuleTest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -74,7 +74,9 @@ public static void main(String args[]) throws Throwable {
7474
Class p2_C2_class = Class.forName("sealedP2.C2");
7575
throw new RuntimeException("Expected IncompatibleClassChangeError exception not thrown");
7676
} catch (IncompatibleClassChangeError e) {
77-
if (!e.getMessage().contains("cannot inherit from sealed class")) {
77+
if (!e.getMessage().equals("Failed same package check: non-public subclass sealedP2.C2 " +
78+
"is in package 'sealedP2' with classloader 'app', and sealed class " +
79+
"sealedP1.SuperClass is in package 'sealedP1' with classloader 'app'")) {
7880
throw new RuntimeException("Wrong IncompatibleClassChangeError exception thrown: " + e.getMessage());
7981
}
8082
}
@@ -86,7 +88,9 @@ public static void main(String args[]) throws Throwable {
8688
Class p3_C3_class = Class.forName("sealedP3.C3");
8789
throw new RuntimeException("Expected IncompatibleClassChangeError exception not thrown");
8890
} catch (IncompatibleClassChangeError e) {
89-
if (!e.getMessage().contains("cannot inherit from sealed class")) {
91+
if (!e.getMessage().equals("Failed same module check: subclass sealedP3.C3 is in module 'module_two' " +
92+
"with loader 'app', and sealed class sealedP1.SuperClass is in module " +
93+
"'module_one' with loader 'app'")) {
9094
throw new RuntimeException("Wrong IncompatibleClassChangeError exception thrown: " + e.getMessage());
9195
}
9296
}

test/hotspot/jtreg/runtime/sealedClasses/GetPermittedSubclassesTest.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -49,6 +49,7 @@ final class NotSealed implements SealedI1 {}
4949

5050
final class Final4 {}
5151

52+
// Check if the given class has the expected permitted subclasses
5253
public static void testSealedInfo(Class<?> c, String[] expected, boolean isSealed) {
5354
var permitted = c.getPermittedSubclasses();
5455

@@ -92,16 +93,16 @@ public static void testSealedInfo(Class<?> c, String[] expected, boolean isSeale
9293

9394
public static void testBadSealedClass(String className,
9495
Class<?> expectedException,
95-
String expectedCFEMessage) throws Throwable {
96+
String expectedMessage) throws Throwable {
9697
try {
9798
Class.forName(className);
98-
throw new RuntimeException("Expected ClassFormatError exception not thrown for " + className);
99+
throw new RuntimeException("Expected exception " + expectedException.getName() + " not thrown for " + className);
99100
} catch (ClassFormatError cfe) {
100101
if (ClassFormatError.class != expectedException) {
101102
throw new RuntimeException(
102103
"Class " + className + " got unexpected exception: " + cfe.getMessage());
103104
}
104-
if (!cfe.getMessage().contains(expectedCFEMessage)) {
105+
if (!cfe.getMessage().contains(expectedMessage)) {
105106
throw new RuntimeException(
106107
"Class " + className + " got unexpected ClassFormatError exception: " + cfe.getMessage());
107108
}
@@ -110,7 +111,7 @@ public static void testBadSealedClass(String className,
110111
throw new RuntimeException(
111112
"Class " + className + " got unexpected exception: " + icce.getMessage());
112113
}
113-
if (!icce.getMessage().contains(expectedCFEMessage)) {
114+
if (!icce.getMessage().contains(expectedMessage)) {
114115
throw new RuntimeException(
115116
"Class " + className + " got unexpected IncompatibleClassChangeError exception: " + icce.getMessage());
116117
}
@@ -135,7 +136,7 @@ public static void main(String... args) throws Throwable {
135136

136137
// Test that a class with an empty PermittedSubclasses attribute cannot be subclass-ed.
137138
testBadSealedClass("SubClass", IncompatibleClassChangeError.class,
138-
"SubClass cannot inherit from sealed class NoSubclasses");
139+
"Failed listed permitted subclass check: class SubClass is not a permitted subclass of NoSubclasses");
139140

140141
// Test returning only names of existing classes.
141142
testSealedInfo(NoLoadSubclasses.class, new String[]{"ExistingClassFile" }, true);

0 commit comments

Comments
 (0)