8145964: NoClassDefFound error in transforming lambdas
Summary: Skip VM anonymous classes in retransformation and give an error for redefinition.
Reviewed-by: dholmes, dcubed, never
--- a/hotspot/src/share/vm/prims/jvmti.xml Fri Aug 19 18:20:22 2016 +0200
+++ b/hotspot/src/share/vm/prims/jvmti.xml Fri Aug 19 14:54:31 2016 -0400
@@ -7152,15 +7152,19 @@
returns <code>JNI_FALSE</code>) the class can be neither
redefined nor retransformed.
<p/>
- Primitive classes (for example, <code>java.lang.Integer.TYPE</code>)
- and array classes are never modifiable.
+ Primitive classes (for example, <code>java.lang.Integer.TYPE</code>),
+ array classes, and some implementation defined classes are never modifiable.
<p/>
</description>
<origin>new</origin>
<capabilities>
<capability id="can_redefine_any_class">
- If possessed then all classes (except primitive and array classes)
- are modifiable.
+ If possessed then all classes (except primitive, array, and some implementation defined
+ classes) are modifiable (redefine or retransform).
+ </capability>
+ <capability id="can_retransform_any_class">
+ If possessed then all classes (except primitive, array, and some implementation defined
+ classes) are modifiable with <functionlink id="RetransformClasses"/>.
</capability>
<capability id="can_redefine_classes">
No effect on the result of the function.
@@ -9900,7 +9904,7 @@
</capabilityfield>
<capabilityfield id="can_redefine_any_class">
<description>
- Can modify (retransform or redefine) any non-primitive non-array class.
+ Can modify (retransform or redefine) any modifiable class.
See <functionlink id="IsModifiableClass"/>.
</description>
</capabilityfield>
@@ -10024,7 +10028,8 @@
</capabilityfield>
<capabilityfield id="can_retransform_any_class" since="1.1">
<description>
- <functionlink id="RetransformClasses"/> can be called on any class
+ <functionlink id="RetransformClasses"/> can be called on any modifiable class.
+ See <functionlink id="IsModifiableClass"/>.
(<fieldlink id="can_retransform_classes" struct="jvmtiCapabilities"/>
must also be set)
</description>
@@ -12494,8 +12499,8 @@
Otherwise, this event may be sent before the VM is initialized (the start
<functionlink id="GetPhase">phase</functionlink>).
Some classes might not be compatible
- with the function (eg. ROMized classes) and this event will not be
- generated for these classes.
+ with the function (eg. ROMized classes or implementation defined classes) and this event will
+ not be generated for these classes.
<p/>
The agent must allocate the space for the modified
class file data buffer
@@ -14498,6 +14503,10 @@
- Add new capability can_generate_early_class_hook_events
- Add new function GetNamedModule
</change>
+ <change date="16 August 2016" version="9.0.0">
+ Clarified can_redefine_any_classes, can_retransform_any_classes and IsModifiableClass API to
+ disallow some implementation defined classes.
+ </change>
</changehistory>
</specification>
--- a/hotspot/src/share/vm/prims/jvmtiEnv.cpp Fri Aug 19 18:20:22 2016 +0200
+++ b/hotspot/src/share/vm/prims/jvmtiEnv.cpp Fri Aug 19 14:54:31 2016 -0400
@@ -283,7 +283,7 @@
return JVMTI_ERROR_INVALID_CLASS;
}
- if (java_lang_Class::is_primitive(k_mirror)) {
+ if (!VM_RedefineClasses::is_modifiable_class(k_mirror)) {
return JVMTI_ERROR_UNMODIFIABLE_CLASS;
}
@@ -294,9 +294,6 @@
if (status & (JVMTI_CLASS_STATUS_ERROR)) {
return JVMTI_ERROR_INVALID_CLASS;
}
- if (status & (JVMTI_CLASS_STATUS_ARRAY)) {
- return JVMTI_ERROR_UNMODIFIABLE_CLASS;
- }
instanceKlassHandle ikh(current_thread, k_oop);
if (ikh->get_cached_class_file_bytes() == NULL) {
--- a/hotspot/src/share/vm/prims/jvmtiRedefineClasses.cpp Fri Aug 19 18:20:22 2016 +0200
+++ b/hotspot/src/share/vm/prims/jvmtiRedefineClasses.cpp Fri Aug 19 14:54:31 2016 -0400
@@ -130,7 +130,7 @@
}
oop mirror = JNIHandles::resolve_non_null(_class_defs[i].klass);
- // classes for primitives and arrays cannot be redefined
+ // classes for primitives and arrays and vm anonymous classes cannot be redefined
// check here so following code can assume these classes are InstanceKlass
if (!is_modifiable_class(mirror)) {
_res = JVMTI_ERROR_UNMODIFIABLE_CLASS;
@@ -250,9 +250,14 @@
if (java_lang_Class::is_primitive(klass_mirror)) {
return false;
}
- Klass* the_class_oop = java_lang_Class::as_Klass(klass_mirror);
+ Klass* k = java_lang_Class::as_Klass(klass_mirror);
// classes for arrays cannot be redefined
- if (the_class_oop == NULL || !the_class_oop->is_instance_klass()) {
+ if (k == NULL || !k->is_instance_klass()) {
+ return false;
+ }
+
+ // Cannot redefine or retransform an anonymous class.
+ if (InstanceKlass::cast(k)->is_anonymous()) {
return false;
}
return true;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hotspot/test/runtime/RedefineTests/ModifyAnonymous.java Fri Aug 19 14:54:31 2016 -0400
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2016, 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
+ * @library /test/lib
+ * @summary Test that retransforming and redefining anonymous classes gets UnmodifiableClassException
+ * @modules java.base/jdk.internal.misc
+ * @modules java.instrument
+ * jdk.jartool/sun.tools.jar
+ * @run main ModifyAnonymous buildagent
+ * @run main/othervm -javaagent:redefineagent.jar ModifyAnonymous
+ */
+
+import java.io.FileNotFoundException;
+import java.io.PrintWriter;
+import java.lang.NoSuchFieldException;
+import java.lang.NoSuchMethodException;
+import java.lang.RuntimeException;
+import java.lang.instrument.ClassDefinition;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
+import java.security.ProtectionDomain;
+import jdk.test.lib.*;
+
+public class ModifyAnonymous {
+
+ public static class LambdaTransformer implements ClassFileTransformer {
+ @Override
+ public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
+ ProtectionDomain protectionDomain, byte[] classfileBuffer)
+ throws IllegalClassFormatException {
+ return null;
+ }
+ }
+
+ static Instrumentation inst = null;
+ static volatile boolean done = false;
+
+ public static void premain(String args, Instrumentation instrumentation) {
+
+ inst = instrumentation;
+ System.out.println("javaagent in da house!");
+ instrumentation.addTransformer(new LambdaTransformer());
+ }
+
+ private static void buildAgent() {
+ try {
+ ClassFileInstaller.main("ModifyAnonymous");
+ } catch (Exception e) {
+ throw new RuntimeException("Could not write agent classfile", e);
+ }
+
+ try {
+ PrintWriter pw = new PrintWriter("MANIFEST.MF");
+ pw.println("Premain-Class: ModifyAnonymous");
+ pw.println("Agent-Class: ModifyAnonymous");
+ pw.println("Can-Retransform-Classes: true");
+ pw.println("Can-Redefine-Classes: true");
+ pw.close();
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException("Could not write manifest file for the agent", e);
+ }
+
+ sun.tools.jar.Main jarTool = new sun.tools.jar.Main(System.out, System.err, "jar");
+ if (!jarTool.run(new String[] { "-cmf", "MANIFEST.MF", "redefineagent.jar", "ModifyAnonymous.class" })) {
+ throw new RuntimeException("Could not write the agent jar file");
+ }
+ }
+
+ public static class InstanceMethodCallSiteApp {
+
+ public static void test() throws InterruptedException {
+ for (int i = 0; i < 2; i++) {
+ InstanceMethodCallSiteApp app = new InstanceMethodCallSiteApp();
+ Runnable r = app::doWork; // this creates an anonymous class
+ while (!done) {
+ r.run();
+ Thread.sleep(10);
+ }
+ }
+ }
+
+ public void doWork() {
+ System.out.print(".");
+ }
+ }
+
+ static void runTest() {
+ while (!done) {
+ Class[] allLoadedClasses = inst.getAllLoadedClasses();
+ for (Class clazz : allLoadedClasses) {
+ final String name = clazz.getName();
+ if (name.contains("$$Lambda$") && name.contains("App")) {
+ if (inst.isModifiableClass(clazz)) {
+ throw new RuntimeException ("Class should not be modifiable");
+ }
+ // Try to modify them anyway.
+ try {
+ System.out.println("retransform called for " + name);
+ inst.retransformClasses(clazz);
+ } catch(java.lang.instrument.UnmodifiableClassException t) {
+ System.out.println("PASSED: expecting UnmodifiableClassException");
+ t.printStackTrace();
+ }
+ try {
+ System.out.println("redefine called for " + name);
+ String newclass = "class Dummy {}";
+ byte[] bytecode = InMemoryJavaCompiler.compile("Dummy", newclass);
+ ClassDefinition cld = new ClassDefinition(clazz, bytecode);
+ inst.redefineClasses(new ClassDefinition[] { cld });
+ } catch(java.lang.instrument.UnmodifiableClassException t) {
+ System.out.println("PASSED: expecting UnmodifiableClassException");
+ t.printStackTrace();
+ } catch(java.lang.ClassNotFoundException e) {
+ throw new RuntimeException ("ClassNotFoundException thrown");
+ }
+ done = true;
+ }
+ }
+ }
+ }
+
+ public static void main(String argv[]) throws InterruptedException, RuntimeException {
+ if (argv.length == 1 && argv[0].equals("buildagent")) {
+ buildAgent();
+ return;
+ }
+
+ if (inst == null) {
+ throw new RuntimeException("Instrumentation object was null");
+ }
+
+ new Thread() {
+ public void run() {
+ runTest();
+ }
+ }.start();
+
+ // Test that NCDFE is not thrown for anonymous class:
+ // ModifyAnonymous$InstanceMethodCallSiteApp$$Lambda$18
+ try {
+ ModifyAnonymous test = new ModifyAnonymous();
+ InstanceMethodCallSiteApp.test();
+ } catch (NoClassDefFoundError e) {
+ throw new RuntimeException("FAILED: NoClassDefFoundError thrown for " + e.getMessage());
+ }
+ System.out.println("PASSED: NoClassDefFound error not thrown");
+ }
+}