50113
|
1 |
/*
|
|
2 |
* Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
|
|
3 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
4 |
*
|
|
5 |
* This code is free software; you can redistribute it and/or modify it
|
|
6 |
* under the terms of the GNU General Public License version 2 only, as
|
|
7 |
* published by the Free Software Foundation. Oracle designates this
|
|
8 |
* particular file as subject to the "Classpath" exception as provided
|
|
9 |
* by Oracle in the LICENSE file that accompanied this code.
|
|
10 |
*
|
|
11 |
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
12 |
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
13 |
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
14 |
* version 2 for more details (a copy is included in the LICENSE file that
|
|
15 |
* accompanied this code).
|
|
16 |
*
|
|
17 |
* You should have received a copy of the GNU General Public License version
|
|
18 |
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
19 |
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
20 |
*
|
|
21 |
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
22 |
* or visit www.oracle.com if you need additional information or have any
|
|
23 |
* questions.
|
|
24 |
*/
|
|
25 |
|
|
26 |
package jdk.jfr.event.io;
|
|
27 |
|
|
28 |
import java.util.Arrays;
|
|
29 |
import java.util.Set;
|
|
30 |
import java.util.HashSet;
|
|
31 |
import java.io.File;
|
|
32 |
import java.security.ProtectionDomain;
|
|
33 |
import java.lang.instrument.ClassFileTransformer;
|
|
34 |
import java.lang.instrument.Instrumentation;
|
|
35 |
import java.lang.instrument.IllegalClassFormatException;
|
|
36 |
|
|
37 |
import jdk.internal.org.objectweb.asm.ClassReader;
|
|
38 |
import jdk.internal.org.objectweb.asm.ClassVisitor;
|
|
39 |
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
|
40 |
import jdk.internal.org.objectweb.asm.ClassWriter;
|
|
41 |
import jdk.internal.org.objectweb.asm.Opcodes;
|
|
42 |
import jdk.internal.org.objectweb.asm.Type;
|
|
43 |
import jdk.test.lib.process.OutputAnalyzer;
|
|
44 |
import jdk.test.lib.process.ProcessTools;
|
|
45 |
|
|
46 |
/*
|
|
47 |
* @test
|
|
48 |
* @summary Test that will instrument the same classes that JFR will also instrument.
|
|
49 |
* @key jfr
|
|
50 |
*
|
|
51 |
* @library /test/lib /test/jdk
|
|
52 |
* @modules java.base/jdk.internal.org.objectweb.asm
|
|
53 |
* java.instrument
|
|
54 |
* jdk.jartool/sun.tools.jar
|
|
55 |
* jdk.jfr
|
|
56 |
*
|
|
57 |
* @run main/othervm jdk.jfr.event.io.TestInstrumentation
|
|
58 |
*/
|
|
59 |
|
|
60 |
// Test that will instrument the same classes that JFR will also instrument.
|
|
61 |
//
|
|
62 |
// The methods that will be instrumented, for example java.io.RandomAccessFile.write,
|
|
63 |
// will add the following code at the start of the method:
|
|
64 |
// InstrumentationCallback.callback("<classname>::<methodname>");
|
|
65 |
//
|
|
66 |
// The class InstrumentationCallback will log all keys added by the callback() function.
|
|
67 |
//
|
|
68 |
// With this instrumentation in place, we will run some existing jfr.io tests
|
|
69 |
// to verify that our instrumentation has not broken the JFR instrumentation.
|
|
70 |
//
|
|
71 |
// After the tests have been run, we verify that the callback() function have been
|
|
72 |
// called from all instrumented classes and methods. This will verify that JFR has not
|
|
73 |
// broken our instrumentation.
|
|
74 |
//
|
|
75 |
// To use instrumentation, the test must be run in a new java process with
|
|
76 |
// the -javaagent option.
|
|
77 |
// We must also create two jars:
|
|
78 |
// TestInstrumentation.jar: The javaagent for the instrumentation.
|
|
79 |
// InstrumentationCallback.jar: This is a separate jar with the instrumentation
|
|
80 |
// callback() function. It is in a separate jar because it must be added to
|
|
81 |
// the bootclasspath to be called from java.io classes.
|
|
82 |
//
|
|
83 |
// The test contains 3 parts:
|
|
84 |
// Setup part that will create jars and launch the new test instance.
|
|
85 |
// Agent part that contains the instrumentation code.
|
|
86 |
// The actual test part is in the TestMain class.
|
|
87 |
//
|
|
88 |
public class TestInstrumentation implements ClassFileTransformer {
|
|
89 |
|
|
90 |
private static Instrumentation instrumentation = null;
|
|
91 |
private static TestInstrumentation testTransformer = null;
|
|
92 |
|
|
93 |
// All methods that will be instrumented.
|
|
94 |
private static final String[] instrMethodKeys = {
|
|
95 |
"java/io/RandomAccessFile::seek::(J)V",
|
|
96 |
"java/io/RandomAccessFile::read::()I",
|
|
97 |
"java/io/RandomAccessFile::read::([B)I",
|
|
98 |
"java/io/RandomAccessFile::write::([B)V",
|
|
99 |
"java/io/RandomAccessFile::write::(I)V",
|
|
100 |
"java/io/RandomAccessFile::close::()V",
|
|
101 |
"java/io/FileInputStream::read::([BII)I",
|
|
102 |
"java/io/FileInputStream::read::([B)I",
|
|
103 |
"java/io/FileInputStream::read::()I",
|
|
104 |
"java/io/FileOutputStream::write::(I)V",
|
|
105 |
"java/io/FileOutputStream::write::([B)V",
|
|
106 |
"java/io/FileOutputStream::write::([BII)V",
|
|
107 |
"java/net/SocketInputStream::read::()I",
|
|
108 |
"java/net/SocketInputStream::read::([B)I",
|
|
109 |
"java/net/SocketInputStream::read::([BII)I",
|
|
110 |
"java/net/SocketInputStream::close::()V",
|
|
111 |
"java/net/SocketOutputStream::write::(I)V",
|
|
112 |
"java/net/SocketOutputStream::write::([B)V",
|
|
113 |
"java/net/SocketOutputStream::write::([BII)V",
|
|
114 |
"java/net/SocketOutputStream::close::()V",
|
|
115 |
"java/nio/channels/FileChannel::read::([Ljava/nio/ByteBuffer;)J",
|
|
116 |
"java/nio/channels/FileChannel::write::([Ljava/nio/ByteBuffer;)J",
|
|
117 |
"java/nio/channels/SocketChannel::open::()Ljava/nio/channels/SocketChannel;",
|
|
118 |
"java/nio/channels/SocketChannel::open::(Ljava/net/SocketAddress;)Ljava/nio/channels/SocketChannel;",
|
|
119 |
"java/nio/channels/SocketChannel::read::([Ljava/nio/ByteBuffer;)J",
|
|
120 |
"java/nio/channels/SocketChannel::write::([Ljava/nio/ByteBuffer;)J",
|
|
121 |
"sun/nio/ch/FileChannelImpl::read::(Ljava/nio/ByteBuffer;)I",
|
|
122 |
"sun/nio/ch/FileChannelImpl::write::(Ljava/nio/ByteBuffer;)I",
|
|
123 |
};
|
|
124 |
|
|
125 |
private static String getInstrMethodKey(String className, String methodName, String signature) {
|
|
126 |
// This key is used to identify a class and method. It is sent to callback(key)
|
|
127 |
return className + "::" + methodName + "::" + signature;
|
|
128 |
}
|
|
129 |
|
|
130 |
private static String getClassFromMethodKey(String methodKey) {
|
|
131 |
return methodKey.split("::")[0];
|
|
132 |
}
|
|
133 |
|
|
134 |
// Set of all classes targeted for instrumentation.
|
|
135 |
private static Set<String> instrClassesTarget = null;
|
|
136 |
|
|
137 |
// Set of all classes where instrumentation has been completed.
|
|
138 |
private static Set<String> instrClassesDone = null;
|
|
139 |
|
|
140 |
static {
|
|
141 |
// Split class names from InstrMethodKeys.
|
|
142 |
instrClassesTarget = new HashSet<String>();
|
|
143 |
instrClassesDone = new HashSet<String>();
|
|
144 |
for (String s : instrMethodKeys) {
|
|
145 |
String className = getClassFromMethodKey(s);
|
|
146 |
instrClassesTarget.add(className);
|
|
147 |
}
|
|
148 |
}
|
|
149 |
|
|
150 |
private static void log(String msg) {
|
|
151 |
System.out.println("TestTransformation: " + msg);
|
|
152 |
}
|
|
153 |
|
|
154 |
|
|
155 |
////////////////////////////////////////////////////////////////////
|
|
156 |
// This is the actual test part.
|
|
157 |
// A batch of jfr io tests will be run twice with a
|
|
158 |
// retransfromClasses() in between. After each test batch we verify
|
|
159 |
// that all callbacks have been called.
|
|
160 |
////////////////////////////////////////////////////////////////////
|
|
161 |
|
|
162 |
public static class TestMain {
|
|
163 |
|
|
164 |
private enum TransformStatus { Transformed, Retransformed, Removed }
|
|
165 |
|
|
166 |
public static void main(String[] args) throws Throwable {
|
|
167 |
runAllTests(TransformStatus.Transformed);
|
|
168 |
|
|
169 |
// Retransform all classes and then repeat tests
|
|
170 |
Set<Class<?>> classes = new HashSet<Class<?>>();
|
|
171 |
for (String className : instrClassesTarget) {
|
|
172 |
Class<?> clazz = Class.forName(className.replaceAll("/", "."));
|
|
173 |
classes.add(clazz);
|
|
174 |
log("Will retransform " + clazz.getName());
|
|
175 |
}
|
|
176 |
instrumentation.retransformClasses(classes.toArray(new Class<?>[0]));
|
|
177 |
|
|
178 |
// Clear all callback keys so we don't read keys from the previous test run.
|
|
179 |
InstrumentationCallback.clear();
|
|
180 |
runAllTests(TransformStatus.Retransformed);
|
|
181 |
|
|
182 |
// Remove my test transformer and run tests again. Should not get any callbacks.
|
|
183 |
instrumentation.removeTransformer(testTransformer);
|
|
184 |
instrumentation.retransformClasses(classes.toArray(new Class<?>[0]));
|
|
185 |
InstrumentationCallback.clear();
|
|
186 |
runAllTests(TransformStatus.Removed);
|
|
187 |
}
|
|
188 |
|
|
189 |
// This is not all available jfr io tests, but a reasonable selection.
|
|
190 |
public static void runAllTests(TransformStatus status) throws Throwable {
|
|
191 |
log("runAllTests, TransformStatus: " + status);
|
|
192 |
try {
|
|
193 |
String[] noArgs = new String[0];
|
|
194 |
TestRandomAccessFileEvents.main(noArgs);
|
|
195 |
TestSocketEvents.main(noArgs);
|
|
196 |
TestSocketChannelEvents.main(noArgs);
|
|
197 |
TestFileChannelEvents.main(noArgs);
|
|
198 |
TestFileStreamEvents.main(noArgs);
|
|
199 |
TestDisabledEvents.main(noArgs);
|
|
200 |
|
|
201 |
// Verify that all expected callbacks have been called.
|
|
202 |
Set<String> callbackKeys = InstrumentationCallback.getKeysCopy();
|
|
203 |
for (String key : instrMethodKeys) {
|
|
204 |
boolean gotCallback = callbackKeys.contains(key);
|
|
205 |
boolean expectsCallback = isClassInstrumented(status, key);
|
|
206 |
String msg = String.format("key:%s, expects:%b", key, expectsCallback);
|
|
207 |
if (gotCallback != expectsCallback) {
|
|
208 |
throw new Exception("Wrong callback() for " + msg);
|
|
209 |
} else {
|
|
210 |
log("Correct callback() for " + msg);
|
|
211 |
}
|
|
212 |
}
|
|
213 |
} catch (Throwable t) {
|
|
214 |
log("Test failed in phase " + status);
|
|
215 |
t.printStackTrace();
|
|
216 |
throw t;
|
|
217 |
}
|
|
218 |
}
|
|
219 |
|
|
220 |
private static boolean isClassInstrumented(TransformStatus status, String key) throws Throwable {
|
|
221 |
switch (status) {
|
|
222 |
case Retransformed:
|
|
223 |
return true;
|
|
224 |
case Removed:
|
|
225 |
return false;
|
|
226 |
case Transformed:
|
|
227 |
String className = getClassFromMethodKey(key);
|
|
228 |
return instrClassesDone.contains(className);
|
|
229 |
}
|
|
230 |
throw new Exception("Test error: Unknown TransformStatus: " + status);
|
|
231 |
}
|
|
232 |
}
|
|
233 |
|
|
234 |
|
|
235 |
////////////////////////////////////////////////////////////////////
|
|
236 |
// This is the setup part. It will create needed jars and
|
|
237 |
// launch a new java instance that will run the internal class TestMain.
|
|
238 |
// This setup step is needed because we must use a javaagent jar to
|
|
239 |
// transform classes.
|
|
240 |
////////////////////////////////////////////////////////////////////
|
|
241 |
|
|
242 |
public static void main(String[] args) throws Throwable {
|
|
243 |
buildJar("TestInstrumentation", true);
|
|
244 |
buildJar("InstrumentationCallback", false);
|
|
245 |
launchTest();
|
|
246 |
}
|
|
247 |
|
|
248 |
private static void buildJar(String jarName, boolean withManifest) throws Throwable {
|
|
249 |
final String slash = File.separator;
|
|
250 |
final String packageName = "jdk/jfr/event/io".replace("/", slash);
|
|
251 |
System.out.println("buildJar packageName: " + packageName);
|
|
252 |
|
|
253 |
String testClasses = System.getProperty("test.classes", "?");
|
|
254 |
String testSrc = System.getProperty("test.src", "?");
|
|
255 |
String jarPath = testClasses + slash + jarName + ".jar";
|
|
256 |
String manifestPath = testSrc + slash + jarName + ".mf";
|
|
257 |
String className = packageName + slash + jarName + ".class";
|
|
258 |
|
|
259 |
String[] args = null;
|
|
260 |
if (withManifest) {
|
|
261 |
args = new String[] {"-cfm", jarPath, manifestPath, "-C", testClasses, className};
|
|
262 |
} else {
|
|
263 |
args = new String[] {"-cf", jarPath, "-C", testClasses, className};
|
|
264 |
}
|
|
265 |
|
|
266 |
log("Running jar " + Arrays.toString(args));
|
|
267 |
sun.tools.jar.Main jarTool = new sun.tools.jar.Main(System.out, System.err, "jar");
|
|
268 |
if (!jarTool.run(args)) {
|
|
269 |
throw new Exception("jar failed: args=" + Arrays.toString(args));
|
|
270 |
}
|
|
271 |
}
|
|
272 |
|
|
273 |
// Launch the test instance. Will run the internal class TestMain.
|
|
274 |
private static void launchTest() throws Throwable {
|
|
275 |
final String slash = File.separator;
|
|
276 |
|
|
277 |
// Need to add jdk/lib/tools.jar to classpath.
|
|
278 |
String classpath =
|
|
279 |
System.getProperty("test.class.path", "") + File.pathSeparator +
|
|
280 |
System.getProperty("test.jdk", ".") + slash + "lib" + slash + "tools.jar";
|
|
281 |
String testClassDir = System.getProperty("test.classes", "") + slash;
|
|
282 |
|
|
283 |
String[] args = {
|
|
284 |
"-Xbootclasspath/a:" + testClassDir + "InstrumentationCallback.jar",
|
|
285 |
"--add-exports", "java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED",
|
|
286 |
"-classpath", classpath,
|
|
287 |
"-javaagent:" + testClassDir + "TestInstrumentation.jar",
|
|
288 |
"jdk.jfr.event.io.TestInstrumentation$TestMain" };
|
|
289 |
OutputAnalyzer output = ProcessTools.executeTestJvm(args);
|
|
290 |
output.shouldHaveExitValue(0);
|
|
291 |
}
|
|
292 |
|
|
293 |
|
|
294 |
////////////////////////////////////////////////////////////////////
|
|
295 |
// This is the java agent part. Used to transform classes.
|
|
296 |
//
|
|
297 |
// Each transformed method will add this call:
|
|
298 |
// InstrumentationCallback.callback("<classname>::<methodname>");
|
|
299 |
////////////////////////////////////////////////////////////////////
|
|
300 |
|
|
301 |
public static void premain(String args, Instrumentation inst) throws Exception {
|
|
302 |
instrumentation = inst;
|
|
303 |
testTransformer = new TestInstrumentation();
|
|
304 |
inst.addTransformer(testTransformer, true);
|
|
305 |
}
|
|
306 |
|
|
307 |
public byte[] transform(
|
|
308 |
ClassLoader classLoader, String className, Class<?> classBeingRedefined,
|
|
309 |
ProtectionDomain pd, byte[] bytes) throws IllegalClassFormatException {
|
|
310 |
// Check if this class should be instrumented.
|
|
311 |
if (!instrClassesTarget.contains(className)) {
|
|
312 |
return null;
|
|
313 |
}
|
|
314 |
|
|
315 |
boolean isRedefinition = classBeingRedefined != null;
|
|
316 |
log("instrument class(" + className + ") " + (isRedefinition ? "redef" : "load"));
|
|
317 |
|
|
318 |
ClassReader reader = new ClassReader(bytes);
|
|
319 |
ClassWriter writer = new ClassWriter(
|
|
320 |
reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
|
321 |
CallbackClassVisitor classVisitor = new CallbackClassVisitor(writer);
|
|
322 |
reader.accept(classVisitor, 0);
|
|
323 |
instrClassesDone.add(className);
|
|
324 |
return writer.toByteArray();
|
|
325 |
}
|
|
326 |
|
|
327 |
private static class CallbackClassVisitor extends ClassVisitor {
|
|
328 |
private String className;
|
|
329 |
|
|
330 |
public CallbackClassVisitor(ClassVisitor cv) {
|
|
331 |
super(Opcodes.ASM5, cv);
|
|
332 |
}
|
|
333 |
|
|
334 |
@Override
|
|
335 |
public void visit(
|
|
336 |
int version, int access, String name, String signature,
|
|
337 |
String superName, String[] interfaces) {
|
|
338 |
cv.visit(version, access, name, signature, superName, interfaces);
|
|
339 |
className = name;
|
|
340 |
}
|
|
341 |
|
|
342 |
@Override
|
|
343 |
public MethodVisitor visitMethod(
|
|
344 |
int access, String methodName, String desc, String signature, String[] exceptions) {
|
|
345 |
String methodKey = getInstrMethodKey(className, methodName, desc);
|
|
346 |
boolean isInstrumentedMethod = Arrays.asList(instrMethodKeys).contains(methodKey);
|
|
347 |
MethodVisitor mv = cv.visitMethod(access, methodName, desc, signature, exceptions);
|
|
348 |
if (isInstrumentedMethod && mv != null) {
|
|
349 |
mv = new CallbackMethodVisitor(mv, methodKey);
|
|
350 |
log("instrumented " + methodKey);
|
|
351 |
}
|
|
352 |
return mv;
|
|
353 |
}
|
|
354 |
}
|
|
355 |
|
|
356 |
public static class CallbackMethodVisitor extends MethodVisitor {
|
|
357 |
private String logMessage;
|
|
358 |
|
|
359 |
public CallbackMethodVisitor(MethodVisitor mv, String logMessage) {
|
|
360 |
super(Opcodes.ASM5, mv);
|
|
361 |
this.logMessage = logMessage;
|
|
362 |
}
|
|
363 |
|
|
364 |
@Override
|
|
365 |
public void visitCode() {
|
|
366 |
mv.visitCode();
|
|
367 |
String methodDescr = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class));
|
|
368 |
String className = InstrumentationCallback.class.getName().replace('.', '/');
|
|
369 |
mv.visitLdcInsn(logMessage);
|
|
370 |
mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, "callback", methodDescr);
|
|
371 |
}
|
|
372 |
}
|
|
373 |
|
|
374 |
}
|