52f1db6b6f
Reviewed-by: coleenp, sspitsyn
286 lines
10 KiB
Java
286 lines
10 KiB
Java
/*
|
|
* Copyright (c) 2012, 2021, 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
|
|
* @bug 7064927
|
|
* @summary Verify LocalVariableTable (LVT) exists when passed to transform() on a retransform operation.
|
|
* @author Daniel D. Daugherty
|
|
*
|
|
* @library /test/lib
|
|
* @run build VerifyLocalVariableTableOnRetransformTest
|
|
* @run compile -g DummyClassWithLVT.java
|
|
* @run shell MakeJAR.sh retransformAgent
|
|
* @run main/othervm -javaagent:retransformAgent.jar VerifyLocalVariableTableOnRetransformTest VerifyLocalVariableTableOnRetransformTest
|
|
*/
|
|
|
|
import java.io.*;
|
|
import java.lang.instrument.Instrumentation;
|
|
import java.lang.instrument.ClassFileTransformer;
|
|
import java.net.*;
|
|
import java.security.ProtectionDomain;
|
|
import java.util.List;
|
|
import java.util.regex.Pattern;
|
|
|
|
import jdk.test.lib.JDKToolLauncher;
|
|
import jdk.test.lib.process.ProcessTools;
|
|
import jdk.test.lib.process.OutputAnalyzer;
|
|
|
|
public class
|
|
VerifyLocalVariableTableOnRetransformTest
|
|
extends ATransformerManagementTestCase
|
|
{
|
|
private byte[] fTargetClassBytes;
|
|
private boolean fTargetClassMatches;
|
|
private String fTargetClassName = "DummyClassWithLVT";
|
|
private String classFileName = fTargetClassName + ".class";
|
|
private boolean fTargetClassSeen;
|
|
|
|
/**
|
|
* Constructor for VerifyLocalVariableTableOnRetransformTest.
|
|
* @param name
|
|
*/
|
|
public VerifyLocalVariableTableOnRetransformTest(String name)
|
|
{
|
|
super(name);
|
|
|
|
File f = originalClassFile();
|
|
System.out.println("Reading test class from " + f);
|
|
try
|
|
{
|
|
InputStream redefineStream = new FileInputStream(f);
|
|
fTargetClassBytes = NamedBuffer.loadBufferFromStream(redefineStream);
|
|
System.out.println("Read " + fTargetClassBytes.length + " bytes.");
|
|
}
|
|
catch (IOException e)
|
|
{
|
|
fail("Could not load the class: " + f.getName());
|
|
}
|
|
}
|
|
|
|
public static void
|
|
main (String[] args)
|
|
throws Throwable {
|
|
ATestCaseScaffold test = new VerifyLocalVariableTableOnRetransformTest(args[0]);
|
|
test.runTest();
|
|
}
|
|
|
|
protected final void
|
|
doRunTest()
|
|
throws Throwable {
|
|
verifyClassFileBuffer();
|
|
}
|
|
|
|
public void
|
|
verifyClassFileBuffer()
|
|
throws Throwable
|
|
{
|
|
beVerbose();
|
|
|
|
// With this call here, we will see the target class twice:
|
|
// first when it gets loaded and second when it gets retransformed.
|
|
addTransformerToManager(fInst, new MyObserver(), true);
|
|
|
|
ClassLoader loader = getClass().getClassLoader();
|
|
|
|
Class target = loader.loadClass(fTargetClassName);
|
|
assertEquals(fTargetClassName, target.getName());
|
|
|
|
// make an instance to prove the class was really loaded
|
|
Object testInstance = target.newInstance();
|
|
|
|
// With this call here, we will see the target class once:
|
|
// when it gets retransformed.
|
|
//addTransformerToManager(fInst, new MyObserver(), true);
|
|
|
|
assertTrue(fTargetClassName + " was not seen by transform()",
|
|
fTargetClassSeen);
|
|
|
|
// The HotSpot VM hands us class file bytes at initial class
|
|
// load time that match the .class file contents. However,
|
|
// according to the following spec that is not required:
|
|
// http://docs.oracle.com/javase/7/docs/api/java/lang/instrument/Instrumentation.html#retransformClasses(java.lang.Class...)
|
|
// This test exists to catch any unintentional change in
|
|
// behavior by the HotSpot VM. If this behavior is intentionally
|
|
// changed in the future, then this test will need to be
|
|
// updated.
|
|
compareClassFileBytes(true);
|
|
|
|
fTargetClassSeen = false;
|
|
fTargetClassMatches = false;
|
|
|
|
fInst.retransformClasses(target);
|
|
|
|
assertTrue(fTargetClassName + " was not seen by transform()",
|
|
fTargetClassSeen);
|
|
|
|
// The HotSpot VM doesn't currently preserve the LocalVariable
|
|
// Table (LVT) attribute so the class file bytes seen by the
|
|
// retransformClasses() call will not match the class file bytes
|
|
// seen at initial class load time.
|
|
compareClassFileBytes(false);
|
|
}
|
|
|
|
private File originalClassFile() {
|
|
return new File(System.getProperty("test.classes", "."), classFileName);
|
|
}
|
|
|
|
private File transformedClassFile() {
|
|
// This file will get created in the test execution
|
|
// directory so there is no conflict with the file
|
|
// in the test classes directory.
|
|
return new File(classFileName);
|
|
}
|
|
|
|
private static final String[] expectedDifferentStrings = {
|
|
"^Classfile .+$",
|
|
"^[\\s]+SHA-256 checksum .[^\\s]+$"
|
|
};
|
|
|
|
private boolean expectedDifferent(String line) {
|
|
for (String s: expectedDifferentStrings) {
|
|
Pattern p = Pattern.compile(s);
|
|
if (p.matcher(line).find()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void compareClassFileBytes(boolean initialLoad) throws Throwable {
|
|
if (fTargetClassMatches) {
|
|
return;
|
|
}
|
|
File f1 = originalClassFile();
|
|
File f2 = transformedClassFile();
|
|
System.out.println(fTargetClassName + " did not match .class file");
|
|
System.out.println("Disassembly difference (" + f1 + " vs " + f2 +"):");
|
|
// compare 'javap -v' output for the class files
|
|
List<String> out1 = disassembleClassFile(f1);
|
|
List<String> out2 = disassembleClassFile(f2);
|
|
boolean different = false;
|
|
boolean orderChanged = false;
|
|
int lineNum = 0;
|
|
for (String line: out1) {
|
|
if (!expectedDifferent(line)) {
|
|
if (!out2.contains(line)) {
|
|
different = true;
|
|
System.out.println("< (" + (lineNum + 1) + ") " + line);
|
|
} else {
|
|
if (lineNum < out2.size() && out1.get(lineNum) != out2.get(lineNum)) {
|
|
// out2 contains line, but at different position
|
|
orderChanged = true;
|
|
}
|
|
}
|
|
}
|
|
lineNum++;
|
|
}
|
|
lineNum = 0;
|
|
for (String line: out2) {
|
|
if (!expectedDifferent(line)) {
|
|
if (!out1.contains(line)) {
|
|
different = true;
|
|
System.out.println("> (" + (lineNum + 1) + ") " + line);
|
|
}
|
|
}
|
|
lineNum++;
|
|
}
|
|
// accordingly the spec orderChanged is fine, but we consider it as error
|
|
// (see comments in verifyClassFileBuffer())
|
|
if (different || orderChanged) {
|
|
fail(fTargetClassName + " (" + (initialLoad ? "load" : "retransform") + ") did not match .class file"
|
|
+ (different ? "" : " (only order changed)"));
|
|
}
|
|
}
|
|
|
|
private List<String> disassembleClassFile(File file) throws Throwable {
|
|
JDKToolLauncher javap = JDKToolLauncher.create("javap")
|
|
.addToolArg("-v")
|
|
.addToolArg(file.toString());
|
|
ProcessBuilder pb = new ProcessBuilder(javap.getCommand());
|
|
OutputAnalyzer out = ProcessTools.executeProcess(pb);
|
|
return out.asLines();
|
|
}
|
|
|
|
public class MyObserver implements ClassFileTransformer {
|
|
public MyObserver() {
|
|
}
|
|
|
|
public String toString() {
|
|
return MyObserver.this.getClass().getName();
|
|
}
|
|
|
|
private void saveMismatchedBytes(byte[] classfileBuffer) {
|
|
try {
|
|
FileOutputStream fos = new FileOutputStream(transformedClassFile());
|
|
fos.write(classfileBuffer);
|
|
fos.close();
|
|
} catch (IOException ex) {
|
|
}
|
|
}
|
|
|
|
public byte[]
|
|
transform(
|
|
ClassLoader loader,
|
|
String className,
|
|
Class<?> classBeingRedefined,
|
|
ProtectionDomain protectionDomain,
|
|
byte[] classfileBuffer) {
|
|
|
|
System.out.println(this + ".transform() sees '" + className
|
|
+ "' of " + classfileBuffer.length + " bytes.");
|
|
if (className.equals(fTargetClassName)) {
|
|
fTargetClassSeen = true;
|
|
|
|
if (classfileBuffer.length != fTargetClassBytes.length) {
|
|
System.out.println("Warning: " + fTargetClassName
|
|
+ " lengths do not match.");
|
|
fTargetClassMatches = false;
|
|
saveMismatchedBytes(classfileBuffer);
|
|
return null;
|
|
} else {
|
|
System.out.println("Info: " + fTargetClassName
|
|
+ " lengths match.");
|
|
}
|
|
|
|
for (int i = 0; i < classfileBuffer.length; i++) {
|
|
if (classfileBuffer[i] != fTargetClassBytes[i]) {
|
|
System.out.println("Warning: " + fTargetClassName
|
|
+ "[" + i + "]: '" + classfileBuffer[i]
|
|
+ "' != '" + fTargetClassBytes[i] + "'");
|
|
fTargetClassMatches = false;
|
|
saveMismatchedBytes(classfileBuffer);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
fTargetClassMatches = true;
|
|
System.out.println("Info: verified '" + fTargetClassName
|
|
+ ".class' matches 'classfileBuffer'.");
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|