# HG changeset patch # User mchung # Date 1523774467 -28800 # Sun Apr 15 14:41:07 2018 +0800 # Node ID 5d0767e9f575c4cb9014a76a7cdad472ca9a88cd # Parent 09c01737ad27dc579bebef35bfe0ce3292cb5339 8200559: Java agents doing instrumentation need a means to define auxiliary classes Reviewed-by: duke diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -161,6 +161,7 @@ jdk.scripting.nashorn; exports jdk.internal.misc to java.desktop, + java.instrument, java.logging, java.management, java.naming, diff --git a/src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java b/src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java --- a/src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java +++ b/src/java.instrument/share/classes/java/lang/instrument/ClassFileTransformer.java @@ -25,8 +25,6 @@ package java.lang.instrument; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.security.ProtectionDomain; /* @@ -142,16 +140,22 @@ * redefinition of the first output class file. * *
+ * The transformers can define additional classes when a class is + * being transformed via {@link ClassDefiner#defineClass(byte[]) + * ClassDefiner.defineClass}. They will be defined to the same class loader and + * the same protection domain as the class being transformed. + * + *
* If the transformer throws an exception (which it doesn't catch),
* subsequent transformers will still be called and the load, redefine
* or retransform will still be attempted.
- * Thus, throwing an exception has the same effect as returning null
.
+ * Thus, throwing an exception has the same effect as returning {@code null}.
* To prevent unexpected behavior when unchecked exceptions are generated
* in transformer code, a transformer can catch Throwable
.
* If the transformer believes the classFileBuffer
does not
* represent a validly formatted class file, it should throw
* an IllegalClassFormatException
;
- * while this has the same effect as returning null. it facilitates the
+ * while this has the same effect as returning {@code null}, it facilitates the
* logging or debugging of format corruptions.
*
*
@@ -165,6 +169,50 @@ */ public interface ClassFileTransformer { + /** + * A {@code ClassDefiner} provides the mechanism for {@code ClassFileTransformer} + * to define additional classes during the + * {@linkplain ClassFileTransformer#transform(ClassDefiner, Module, ClassLoader, String, Class, ProtectionDomain, byte[]) + * transformation process} of a class. + * + *
When a {@linkplain ClassFileTransformer#transform(ClassDefiner, Module, ClassLoader, String, Class, ProtectionDomain, byte[]) + * transformer} is called to retransform a class, the transformer can call + * {@link ClassDefiner#defineClass(byte[])} to define additional classes. + * If it is called after {@link ClassFileTransformer#transform(ClassDefiner, Module, ClassLoader, String, Class, ProtectionDomain, byte[]) + * ClassFileTransformer.tranform} method returns or called by another + * thread, {@code IllegalStateException} will be thrown. + * + * @since 11 + */ + static interface ClassDefiner { + /** + * Defines a class of the given bytes to the runtime. + * The given bytes will be defined to the same class loader and in the + * same runtime package and + * {@linkplain java.security.ProtectionDomain protection domain} as + * the class being transformed. + * + *
No transformation is applied to the bytes passed to this + * {@code ClassDefiner.defineClass} method. + * {@link Instrumentation#retransformClasses(Class[])} can be called + * later to retransform the {@code Class} returned by this method + * if desired. + * + * @param bytes the class bytes + * @return the {@code Class} object for the class + * + * @throws IllegalArgumentException + * the bytes are for a class of a different package than + * the class being transformed + * @throws LinkageError if the class is malformed ({@code ClassFormatError}), + * cannot be verified ({@code VerifyError}), is already defined, + * or another linkage error occurs + * @throws IllegalStateException if this {@code ClassDefiner} is called + * by a thread other than the thread doing the transformation, + * or after all transformers are called. + */ + Class> defineClass(byte[] bytes); + } /** * Transforms the given class file and returns a new replacement class file. @@ -249,4 +297,59 @@ protectionDomain, classfileBuffer); } + + /** + * Transforms the given class file and returns a new replacement class file. + * + *
A {@code ClassFileTransformer} may define additional classes
+ * to the same class loader and the same runtime package as the class being
+ * transformed by calling {@link ClassDefiner#defineClass(byte[])}
+ * classDefiner.defineClass} method. If an attempt to use {@code classDefiner}
+ * is made after this {@code transform} method returns,
+ * {@code IllegalStateException} will be thrown.
+ *
+ * @implSpec The default implementation of this method invokes the
+ * {@link #transform(Module,ClassLoader,String,Class,ProtectionDomain,byte[]) transform}
+ * method.
+ *
+ * @param classDefiner a {@code ClassDefiner} that this class transformer
+ * can define additional classes
+ * @param module the module of the class to be transformed
+ * @param loader the defining loader of the class to be transformed,
+ * may be {@code null} if the bootstrap loader
+ * @param className the name of the class in the internal form of fully
+ * qualified class and interface names as defined in
+ * The Java Virtual Machine Specification.
+ * For example, "java/util/List"
.
+ * @param classBeingRedefined if this is triggered by a redefine or retransform,
+ * the class being redefined or retransformed;
+ * if this is a class load, {@code null}
+ * @param protectionDomain the protection domain of the class being defined or redefined
+ * @param classfileBuffer the input byte buffer in class file format - must not be modified
+ *
+ * @throws IllegalClassFormatException
+ * if the input does not represent a well-formed class file
+ * @return a well-formed class file buffer (the result of the transform),
+ * or {@code null} if no transform is performed
+ *
+ * @since 11
+ */
+ default byte[]
+ transform( ClassFileTransformer.ClassDefiner classDefiner,
+ Module module,
+ ClassLoader loader,
+ String className,
+ Class> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer)
+ throws IllegalClassFormatException {
+
+ // invoke the transform method
+ return transform(module,
+ loader,
+ className,
+ classBeingRedefined,
+ protectionDomain,
+ classfileBuffer);
+ }
}
diff --git a/src/java.instrument/share/classes/sun/instrument/CloseableClassDefiner.java b/src/java.instrument/share/classes/sun/instrument/CloseableClassDefiner.java
new file mode 100644
--- /dev/null
+++ b/src/java.instrument/share/classes/sun/instrument/CloseableClassDefiner.java
@@ -0,0 +1,100 @@
+/*
+ * 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 sun.instrument;
+
+import jdk.internal.misc.SharedSecrets;
+import jdk.internal.org.objectweb.asm.ClassReader;
+
+import java.lang.instrument.ClassFileTransformer.ClassDefiner;
+import java.security.ProtectionDomain;
+import java.util.Objects;
+
+
+class CloseableClassDefiner implements ClassDefiner, AutoCloseable {
+ final Thread thread;
+ final String packageName;
+ final ClassLoader loader;
+ final ProtectionDomain protectionDomain;
+ volatile boolean active;
+
+ CloseableClassDefiner(String classname, ClassLoader loader, ProtectionDomain pd) {
+ this.thread = Thread.currentThread();
+ int index = classname.lastIndexOf("/");
+ this.packageName = index == -1 ? "" : classname.substring(0, index).replace('/', '.');
+ this.loader = loader;
+ this.protectionDomain = pd;
+ this.active = true;
+ }
+
+ @Override
+ public Class> defineClass(byte[] bytes) {
+ Objects.requireNonNull(bytes);
+ if (!active || Thread.currentThread() != thread) {
+ throw new IllegalStateException("this ClassDefiner is not valid");
+ }
+
+ // parse class bytes to get class name (in internal form)
+ bytes = bytes.clone();
+ String name;
+
+ try {
+ ClassReader reader = new ClassReader(bytes);
+ name = reader.getClassName();
+ } catch (RuntimeException e) {
+ // ASM exceptions are poorly specified
+ ClassFormatError cfe = new ClassFormatError();
+ cfe.initCause(e);
+ throw cfe;
+ }
+
+ // get package and class name in binary form
+ String cn, pn;
+ int index = name.lastIndexOf('/');
+ if (index == -1) {
+ cn = name;
+ pn = "";
+ } else {
+ cn = name.replace('/', '.');
+ pn = cn.substring(0, index);
+ }
+
+ if (!packageName.equals(pn)) {
+ throw new IllegalArgumentException(cn + " not in same package as the class being transformed");
+ }
+
+ /*
+ * Defines auxiliary class
+ */
+ String source = "__ClassFileTransformer_defineClass__";
+ return SharedSecrets.getJavaLangAccess().defineClass(loader, cn, bytes, protectionDomain, source);
+ }
+
+ @Override
+ public void close() {
+ this.active = false;
+ }
+
+}
diff --git a/src/java.instrument/share/classes/sun/instrument/TransformerManager.java b/src/java.instrument/share/classes/sun/instrument/TransformerManager.java
--- a/src/java.instrument/share/classes/sun/instrument/TransformerManager.java
+++ b/src/java.instrument/share/classes/sun/instrument/TransformerManager.java
@@ -25,7 +25,6 @@
package sun.instrument;
-import java.lang.instrument.Instrumentation;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
@@ -174,35 +173,36 @@
byte[] classfileBuffer) {
boolean someoneTouchedTheBytecode = false;
- TransformerInfo[] transformerList = getSnapshotTransformerList();
+ TransformerInfo[] transformerList = getSnapshotTransformerList();
+ byte[] bufferToUse = classfileBuffer;
- byte[] bufferToUse = classfileBuffer;
+ try (CloseableClassDefiner classDefiner = new CloseableClassDefiner(classname, loader, protectionDomain)) {
+ // order matters, gotta run 'em in the order they were added
+ for (int x = 0; x < transformerList.length; x++) {
+ TransformerInfo transformerInfo = transformerList[x];
+ ClassFileTransformer transformer = transformerInfo.transformer();
+ byte[] transformedBytes = null;
+ try {
+ transformedBytes = transformer.transform(classDefiner,
+ module,
+ loader,
+ classname,
+ classBeingRedefined,
+ protectionDomain,
+ bufferToUse);
+ } catch (Throwable t) {
+ // don't let any one transformer mess it up for the others.
+ // This is where we need to put some logging. What should go here? FIXME
+ }
- // order matters, gotta run 'em in the order they were added
- for ( int x = 0; x < transformerList.length; x++ ) {
- TransformerInfo transformerInfo = transformerList[x];
- ClassFileTransformer transformer = transformerInfo.transformer();
- byte[] transformedBytes = null;
-
- try {
- transformedBytes = transformer.transform( module,
- loader,
- classname,
- classBeingRedefined,
- protectionDomain,
- bufferToUse);
- }
- catch (Throwable t) {
- // don't let any one transformer mess it up for the others.
- // This is where we need to put some logging. What should go here? FIXME
- }
-
- if ( transformedBytes != null ) {
- someoneTouchedTheBytecode = true;
- bufferToUse = transformedBytes;
+ if (transformedBytes != null) {
+ someoneTouchedTheBytecode = true;
+ bufferToUse = transformedBytes;
+ }
}
}
+
// if someone modified it, return the modified buffer.
// otherwise return null to mean "no transforms occurred"
byte [] result;
@@ -216,7 +216,6 @@
return result;
}
-
int
getTransformerCount() {
TransformerInfo[] transformerList = getSnapshotTransformerList();
diff --git a/test/jdk/java/lang/instrument/ATransformerManagementTestCase.java b/test/jdk/java/lang/instrument/ATransformerManagementTestCase.java
--- a/test/jdk/java/lang/instrument/ATransformerManagementTestCase.java
+++ b/test/jdk/java/lang/instrument/ATransformerManagementTestCase.java
@@ -316,6 +316,28 @@
protectionDomain,
classfileBuffer);
}
+
+ public byte[]
+ transform(
+ ClassFileTransformer.ClassDefiner classDefiner,
+ Module module,
+ ClassLoader loader,
+ String className,
+ Class> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) {
+
+ // The transform testing is triggered by redefine, ignore others
+ if (classBeingRedefined != null) checkInTransformer(MyClassFileTransformer.this);
+
+ return super.transform( classDefiner,
+ module,
+ loader,
+ className,
+ classBeingRedefined,
+ protectionDomain,
+ classfileBuffer);
+ }
}
diff --git a/test/jdk/java/lang/instrument/BootstrapClassPathAgent.java b/test/jdk/java/lang/instrument/BootstrapClassPathAgent.java
--- a/test/jdk/java/lang/instrument/BootstrapClassPathAgent.java
+++ b/test/jdk/java/lang/instrument/BootstrapClassPathAgent.java
@@ -25,6 +25,7 @@
import java.io.*;
import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.ClassFileTransformer.ClassDefiner;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
@@ -37,7 +38,8 @@
static class MyTransformer implements ClassFileTransformer {
- public byte[] transform(Module module,
+ public byte[] transform(ClassDefiner classDefiner,
+ Module module,
String className,
Class> classBeingRedefined,
ProtectionDomain protectionDomain,
diff --git a/test/jdk/java/lang/instrument/ClassDefiner/DefineAuxClassApp.java b/test/jdk/java/lang/instrument/ClassDefiner/DefineAuxClassApp.java
new file mode 100644
--- /dev/null
+++ b/test/jdk/java/lang/instrument/ClassDefiner/DefineAuxClassApp.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ *
+ * 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 p;
+
+import java.io.PrintStream;
+import java.util.*;
+
+public class DefineAuxClassApp {
+
+ public static void main(String args[]) throws Exception {
+ (new DefineAuxClassApp()).run(args, System.out);
+ }
+
+ int foo(int x) {
+ return x * x;
+ }
+
+ public void run(String args[], PrintStream out) throws Exception {
+ out.println("start");
+ for (int i = 0; i < 4; i++) {
+ if (foo(3) != 9) {
+ throw new Exception("ERROR: unexpected application behavior");
+ }
+ }
+ out.println("undo");
+ DefineAuxClassTest.undo();
+ for (int i = 0; i < 4; i++) {
+ if (foo(3) != 9) {
+ throw new Exception("ERROR: unexpected application behavior");
+ }
+ }
+ out.println("end");
+ if (DefineAuxClassTest.succeeded()) {
+ out.println("Instrumentation succeeded.");
+ } else {
+ throw new Exception("ERROR: Instrumentation failed.");
+ }
+ }
+}
diff --git a/test/jdk/java/lang/instrument/ClassDefiner/DefineAuxClassTest.java b/test/jdk/java/lang/instrument/ClassDefiner/DefineAuxClassTest.java
new file mode 100644
--- /dev/null
+++ b/test/jdk/java/lang/instrument/ClassDefiner/DefineAuxClassTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.
+ *
+ * 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 auxiliary classes
+ *
+ * @modules java.base/jdk.internal.org.objectweb.asm
+ * java.instrument
+ * @compile ../asmlib/Instrumentor.java DefineAuxClassTest.java DefineAuxClassApp.java
+ * @run shell/timeout=240 MakeAgent.sh 'Can-Retransform-Classes: true'
+ * @run main/othervm -javaagent:DefineAuxClassTest.jar p.DefineAuxClassApp
+ */
+
+package p;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.ClassFileTransformer.ClassDefiner;
+import java.lang.instrument.Instrumentation;
+import java.lang.instrument.IllegalClassFormatException;
+
+import java.security.ProtectionDomain;
+import java.util.stream.Stream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.nio.file.*;
+import java.io.IOException;
+import java.io.OutputStream;
+import jdk.internal.org.objectweb.asm.ClassReader;
+import jdk.internal.org.objectweb.asm.ClassVisitor;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+import jdk.internal.org.objectweb.asm.Opcodes;
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+import asmlib.*;
+
+class DefineAuxClassTest {
+
+ static ClassFileTransformer t1, t2, t3, t4;
+ static Instrumentation inst;
+ static boolean succeeded = true;
+ static int markCount = 0;
+ static int[] markGolden = {30, 40, 20, 30, 40, 20, 30, 40, 20, 30, 40, 20,
+ 11, 40, 20, 11, 40, 20, 11, 40, 20, 11, 40, 20};
+
+ static class Tr implements ClassFileTransformer {
+ final String trname;
+ final boolean onLoad;
+ final int loadIndex;
+ final boolean onRedef;
+ final int redefIndex;
+ final String cname;
+ final String nname;
+ final String auxClassHelperName;
+ final AtomicBoolean auxClassDefined;
+
+ Tr(String trname, boolean onLoad, int loadIndex, boolean onRedef, int redefIndex,
+ String cname, String nname) {
+ this.trname = trname;
+ this.onLoad = onLoad;
+ this.loadIndex = loadIndex;
+ this.onRedef = onRedef;
+ this.redefIndex = redefIndex;
+ this.cname = cname;
+ this.nname = nname;
+
+ int index = cname.lastIndexOf(".");
+ if (index == -1) {
+ this.auxClassHelperName = trname + "AuxClassHelper";
+ } else {
+ String pn = cname.substring(0, index);
+ this.auxClassHelperName = pn + "." + trname + "AuxClassHelper";
+
+ }
+ this.auxClassDefined = new AtomicBoolean(false);
+ }
+
+ public byte[] transform(ClassDefiner classDefiner,
+ Module module,
+ ClassLoader loader,
+ String className,
+ Class> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) throws IllegalClassFormatException {
+ boolean redef = classBeingRedefined != null;
+ String name = className.replace('/', '.');
+ if ((redef? onRedef : onLoad) && className != null && name.equals(cname)) {
+ byte[] bytes = auxClassHelper();
+ if (!auxClassDefined.getAndSet(true)) {
+ // first time defining auxiliary class
+ Class> c = classDefiner.defineClass(bytes);
+ if (!c.getName().equals(auxClassHelperName)) {
+ throw new RuntimeException("unexpected class defined: " + c);
+ }
+ } else {
+ try {
+ Class> c = classDefiner.defineClass(bytes);
+ throw new RuntimeException(auxClassHelperName + " should fail to be defined");
+ } catch (Throwable e) {}
+ }
+
+ int fixedIndex = redef ? redefIndex : loadIndex;
+ try {
+ String internalName = auxClassHelperName.replace('.', '/');
+ byte[] newcf = Instrumentor.instrFor(classfileBuffer)
+ .addMethodEntryInjection(
+ nname,
+ (h)->{
+ h.push(fixedIndex);
+ h.invokeStatic(internalName, "callTracker", "(I)V", false);
+ })
+ .apply();
+ System.err.println(trname + ": " + className + " index: " + fixedIndex +
+ (redef? " REDEF" : " LOAD") +
+ " len before: " + classfileBuffer.length +
+ " after: " + newcf.length);
+ return newcf;
+ } catch (Throwable ex) {
+ System.err.println("Injection failure: " + ex);
+ ex.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Generates the auxiliary class
+ */
+ byte[] auxClassHelper() {
+ ClassWriter cw = new ClassWriter(0);
+ String internalName = auxClassHelperName.replace('.', '/');
+ cw.visit(V1_8, ACC_PUBLIC, internalName, null, "java/lang/Object", null);
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "callTracker", "(I)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitMethodInsn(INVOKESTATIC, "p/DefineAuxClassTest", "callTracker", "(I)V");
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+
+ static void write_buffer(String fname, byte[] buffer) {
+ try {
+ Path path = Paths.get(fname);
+ if (path.getParent() != null) {
+ Files.createDirectories(path.getParent());
+ }
+ try (OutputStream outStream = Files.newOutputStream(path)) {
+ outStream.write(buffer, 0, buffer.length);
+ }
+ } catch (IOException ex) {
+ System.err.println("EXCEPTION in write_buffer: " + ex);
+ }
+ }
+
+ public static void premain(String agentArgs, Instrumentation instArg) {
+ inst = instArg;
+ System.err.println("Premain");
+
+ t1 = new Tr("TR1", false, 10, true, 11, "p.DefineAuxClassApp", "foo");
+ inst.addTransformer(t1, true);
+ t2 = new Tr("TN2", true, 20, true, 21, "p.DefineAuxClassApp", "foo");
+ inst.addTransformer(t2, false);
+ t3 = new Tr("TR3", true, 30, true, 31, "p.DefineAuxClassApp", "foo");
+ inst.addTransformer(t3, true);
+ t4 = new Tr("TN4", true, 40, true, 41, "p.DefineAuxClassApp", "foo");
+ inst.addTransformer(t4, false);
+ }
+
+ public static void undo() {
+ inst.removeTransformer(t3);
+ try {
+ System.err.println("RETRANSFORM");
+ inst.retransformClasses(new DefineAuxClassApp().getClass());
+ } catch (Exception ex) {
+ System.err.println("EXCEPTION on undo: " + ex);
+ }
+ }
+
+ public static boolean succeeded() {
+ return succeeded && markCount == markGolden.length;
+ }
+
+ public static void callTracker(int mark) {
+ System.err.println("got mark " + mark);
+ if (markCount >= markGolden.length || mark != markGolden[markCount++]) {
+ succeeded = false;
+ }
+ }
+
+}
diff --git a/test/jdk/java/lang/instrument/ClassDefiner/MakeAgent.sh b/test/jdk/java/lang/instrument/ClassDefiner/MakeAgent.sh
new file mode 100644
--- /dev/null
+++ b/test/jdk/java/lang/instrument/ClassDefiner/MakeAgent.sh
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+#
+# 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.
+#
+# 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.
+#
+
+if [ "${TESTSRC}" = "" ]
+then
+ echo "TESTSRC not set. Test cannot execute. Failed."
+ exit 1
+fi
+echo "TESTSRC=${TESTSRC}"
+
+if [ "${TESTJAVA}" = "" ]
+then
+ echo "TESTJAVA not set. Test cannot execute. Failed."
+ exit 1
+fi
+echo "TESTJAVA=${TESTJAVA}"
+
+if [ "${COMPILEJAVA}" = "" ]
+then
+ COMPILEJAVA="${TESTJAVA}"
+fi
+echo "COMPILEJAVA=${COMPILEJAVA}"
+
+if [ "${TESTCLASSES}" = "" ]
+then
+ echo "TESTCLASSES not set. Test cannot execute. Failed."
+ exit 1
+fi
+
+OS=`uname -s`
+case "$OS" in
+ SunOS | Linux | Darwin )
+ PATHSEP=":"
+ ;;
+
+ Windows* | CYGWIN*)
+ PATHSEP=";"
+ ;;
+
+ # catch all other OSs
+ * )
+ echo "Unrecognized system! $OS"
+ fail "Unrecognized system! $OS"
+ ;;
+esac
+
+JAR="${COMPILEJAVA}/bin/jar"
+
+echo "Manifest-Version: 1.0" > agent.mf
+echo Premain-Class: p.DefineAuxClassTest >> agent.mf
+shift 2
+while [ $# != 0 ] ; do
+ echo $1 >> agent.mf
+ shift
+done
+
+${JAR} ${TESTTOOLVMOPTS} cvfm DefineAuxClassTest.jar agent.mf \
+ -C ${TESTCLASSES} p/DefineAuxClassTest.class \
+ -C ${TESTCLASSES} p/DefineAuxClassTest\$Tr.class \
+ -C ${TESTCLASSES} asmlib
+rm -rf ${TESTCLASSES}/p/DefineAuxClassTest.class ${TESTCLASSES}/asmlib
+
diff --git a/test/jdk/java/lang/instrument/MakeJAR2.sh b/test/jdk/java/lang/instrument/MakeJAR2.sh
--- a/test/jdk/java/lang/instrument/MakeJAR2.sh
+++ b/test/jdk/java/lang/instrument/MakeJAR2.sh
@@ -23,7 +23,6 @@
# questions.
#
-
AGENT="$1"
APP="$2"
@@ -55,7 +54,7 @@
OS=`uname -s`
case "$OS" in
- SunOS | Linux )
+ SunOS | Linux | Darwin )
PATHSEP=":"
;;
diff --git a/test/jdk/java/lang/instrument/SimpleIdentityTransformer.java b/test/jdk/java/lang/instrument/SimpleIdentityTransformer.java
--- a/test/jdk/java/lang/instrument/SimpleIdentityTransformer.java
+++ b/test/jdk/java/lang/instrument/SimpleIdentityTransformer.java
@@ -72,4 +72,19 @@
return newBuffer;
}
+
+ public byte[]
+ transform(
+ ClassFileTransformer.ClassDefiner classDefiner,
+ Module module,
+ ClassLoader loader,
+ String className,
+ Class> classBeingRedefined,
+ ProtectionDomain protectionDomain,
+ byte[] classfileBuffer) {
+ byte[] newBuffer = new byte[classfileBuffer.length];
+ System.arraycopy(classfileBuffer, 0, newBuffer, 0, classfileBuffer.length);
+
+ return newBuffer;
+ }
}