mirror of
https://github.com/wahyd4/pinpoint.git
synced 2026-08-17 00:36:02 +10:00
#1807 Javassist bug : Incorrect StackMap Frame generation
- add bug regression testcase
This commit is contained in:
@@ -36,6 +36,22 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navercorp.pinpoint</groupId>
|
||||
<artifactId>pinpoint-profiler</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.javassist</groupId>
|
||||
<artifactId>javassist</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ow2.asm</groupId>
|
||||
<artifactId>asm-debug-all</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package bug_regression_jdk7.javassist;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
/**
|
||||
* @author Woonduk Kang(emeroad)
|
||||
*/
|
||||
public class CustomURLClassLoader extends URLClassLoader {
|
||||
|
||||
public CustomURLClassLoader(URL[] urls, ClassLoader parent) {
|
||||
super(urls, parent);
|
||||
}
|
||||
|
||||
public CustomURLClassLoader(URL[] urls) {
|
||||
super(urls);
|
||||
}
|
||||
|
||||
public Class<?> defineClass0(String name, byte[] bytes) {
|
||||
return this.defineClass(name, bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
public Class<?> defineClass0(String name, byte[] bytes, int off, int length) {
|
||||
return super.defineClass(name, bytes, off, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
return super.loadClass(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package bug_regression_jdk7.javassist;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Woonduk Kang(emeroad)
|
||||
*/
|
||||
public class InvalidStackMapFrame {
|
||||
|
||||
public void bytecodeVerifyError() {
|
||||
// javassist bug : invalid stack map frame
|
||||
List<Integer> test = new ArrayList<Integer>();
|
||||
String[] newLine = new String[10];
|
||||
for (Integer idx : test) {
|
||||
String address = newLine[1];
|
||||
int tabPos = -1;
|
||||
if (tabPos != -1) {
|
||||
address = address.substring(tabPos + 1);
|
||||
}
|
||||
newLine[4] = address;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package bug_regression_jdk7.javassist;
|
||||
|
||||
import bug_regression_jdk7.javassist.asm.BytecodeVerifyTestClassVisitor;
|
||||
import com.navercorp.pinpoint.profiler.instrument.ASMBytecodeDisassembler;
|
||||
import com.navercorp.pinpoint.profiler.util.JavaAssistUtils;
|
||||
import javassist.ClassPool;
|
||||
import javassist.CtClass;
|
||||
import javassist.CtMethod;
|
||||
import javassist.LoaderClassPath;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* @author Woonduk Kang(emeroad)
|
||||
*/
|
||||
public class JavassistVerifyErrorTest {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private static final String INVALID_STACK_MAP_FRAME = "bug_regression_jdk7.javassist.InvalidStackMapFrame";
|
||||
|
||||
/**
|
||||
* bug id
|
||||
* https://github.com/naver/pinpoint/issues/1807
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
public void bug_regression_BytecodeVerifyError_Invalid_StackMapFrame() throws Exception {
|
||||
|
||||
CustomURLClassLoader classLoader = new CustomURLClassLoader(new URL[]{}, Thread.currentThread().getContextClassLoader());
|
||||
|
||||
ClassPool classPool = new ClassPool(true);
|
||||
classPool.appendClassPath(new LoaderClassPath(classLoader));
|
||||
|
||||
final CtClass ctClass = classPool.get(INVALID_STACK_MAP_FRAME);
|
||||
final CtMethod method = ctClass.getDeclaredMethod("bytecodeVerifyError");
|
||||
method.addLocalVariable("test_localVariable", CtClass.intType);
|
||||
method.insertBefore("{ test_localVariable = 1; }");
|
||||
|
||||
final byte[] bytecode = ctClass.toBytecode();
|
||||
classLoader.defineClass0(INVALID_STACK_MAP_FRAME, bytecode);
|
||||
try {
|
||||
Class.forName(INVALID_STACK_MAP_FRAME, true, classLoader);
|
||||
Assert.fail("VerifyError");
|
||||
} catch (java.lang.VerifyError e) {
|
||||
logger.debug("verifyError:{}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
||||
final ASMBytecodeDisassembler bytecodeDisassembler = new ASMBytecodeDisassembler();
|
||||
|
||||
final String dumpBytecode = bytecodeDisassembler.dumpBytecode(bytecode);
|
||||
logger.debug("dumpBytecode:{}", dumpBytecode);
|
||||
|
||||
// javassist bug : invalid stack map frame
|
||||
// 00013 InvalidStackMapFrame ArrayList String Iterator I : : FRAME FULL [bug_regression_jdk7/javassist/InvalidStackMapFrame java/util/ArrayList [[[java/lang/Object->[Ljava/lang/String;]]] java/util/Iterator T T T I] []
|
||||
final String verify = bytecodeDisassembler.dumpVerify(bytecode, classLoader);
|
||||
logger.debug("dumpVerify:{}", verify);
|
||||
|
||||
final String dumpAsm = bytecodeDisassembler.dumpASM(bytecode);
|
||||
logger.debug("dumpAsm :{}", dumpAsm);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void asm_stackmapframe_check() throws Exception {
|
||||
|
||||
CustomURLClassLoader classLoader = new CustomURLClassLoader(new URL[]{}, Thread.currentThread().getContextClassLoader());
|
||||
final InputStream stream = classLoader.getResourceAsStream(JavaAssistUtils.javaNameToJvmName(INVALID_STACK_MAP_FRAME) + ".class");
|
||||
|
||||
ClassReader cr = new ClassReader(stream);
|
||||
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
||||
ClassVisitor cv = new BytecodeVerifyTestClassVisitor(cw);
|
||||
cr.accept(cv, ClassReader.EXPAND_FRAMES | ClassReader.SKIP_DEBUG);
|
||||
|
||||
byte[] bytecode = cw.toByteArray();
|
||||
classLoader.defineClass0(INVALID_STACK_MAP_FRAME, bytecode);
|
||||
|
||||
final Class<?> aClass = Class.forName(INVALID_STACK_MAP_FRAME, true, classLoader);
|
||||
Assert.assertSame(aClass.getClassLoader(), classLoader);
|
||||
|
||||
|
||||
final ASMBytecodeDisassembler bytecodeDisassembler = new ASMBytecodeDisassembler();
|
||||
|
||||
final String dumpBytecode = bytecodeDisassembler.dumpBytecode(bytecode);
|
||||
logger.debug("dumpBytecode:{}", dumpBytecode);
|
||||
|
||||
final String verify = bytecodeDisassembler.dumpVerify(bytecode, classLoader);
|
||||
logger.debug("dumpVerify:{}", verify);
|
||||
|
||||
// final String dumpAsm = bytecodeDisassembler.dumpASM(bytecode);
|
||||
// logger.debug("dumpAsm :{}", dumpAsm);
|
||||
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package bug_regression_jdk7.javassist.asm;
|
||||
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.commons.AdviceAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Woonduk Kang(emeroad)
|
||||
*/
|
||||
public class AddIntVariableMethodAdapter extends AdviceAdapter {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private String name;
|
||||
|
||||
public AddIntVariableMethodAdapter(MethodVisitor mv, int acc, String name, String desc) {
|
||||
super(Opcodes.ASM5, mv, acc, name, desc);
|
||||
this.name = name;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMaxs(int maxStack, int maxLocals) {
|
||||
super.visitMaxs(maxStack + 1, maxLocals);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMethodEnter() {
|
||||
logger.debug("onMethodEnter() add int local variable inst:ISTORE_7 {}", name);
|
||||
mv.visitInsn(Opcodes.ICONST_0);
|
||||
mv.visitVarInsn(Opcodes.ISTORE, 7);
|
||||
}
|
||||
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package bug_regression_jdk7.javassist.asm;
|
||||
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
|
||||
|
||||
/**
|
||||
* @author Woonduk Kang(emeroad)
|
||||
*/
|
||||
public class BytecodeVerifyTestClassVisitor extends ClassVisitor {
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
private String name;
|
||||
|
||||
public BytecodeVerifyTestClassVisitor(final ClassVisitor cv) {
|
||||
super(org.objectweb.asm.Opcodes.ASM5, cv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(final int version, final int access, final String name,
|
||||
final String signature, final String superName,
|
||||
final String[] interfaces) {
|
||||
this.name = name;
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(final int access, final String name,
|
||||
final String desc, final String signature, final String[] exceptions) {
|
||||
|
||||
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
|
||||
logger.debug("mv:{}", mv);
|
||||
logger.debug("name:{}", name);
|
||||
logger.debug("desc:{}", desc);
|
||||
logger.debug("signature:{}", signature);
|
||||
if (name.contains("bytecodeVerifyError")) {
|
||||
return new AddIntVariableMethodAdapter(mv, Opcodes.ACC_PUBLIC, name, "()V");
|
||||
}
|
||||
return mv;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE log4j:configuration PUBLIC "" "log4j.dtd">
|
||||
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>
|
||||
|
||||
<appender name="console" class="org.apache.log4j.ConsoleAppender">
|
||||
<layout class="org.apache.log4j.EnhancedPatternLayout">
|
||||
<param name="ConversionPattern" value="%d{HH:mm:ss} [%-5p](%-35c{1.}:%-3L) %m%n" />
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<appender name="dailyRollingFile" class="org.apache.log4j.DailyRollingFileAppender">
|
||||
<param name="file" value="${pinpoint.log}/pinpoint.log" />
|
||||
<param name="Append" value="true" />
|
||||
<param name="DatePattern" value="'.'yyyy-MM-dd"/>
|
||||
|
||||
<layout class="org.apache.log4j.EnhancedPatternLayout">
|
||||
<param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} [%-5p](%-35c{1.}) %m%n" />
|
||||
</layout>
|
||||
|
||||
</appender>
|
||||
|
||||
|
||||
|
||||
<logger name="com.navercorp.pinpoint" additivity="false">
|
||||
<level value="DEBUG" />
|
||||
<appender-ref ref="console" />
|
||||
</logger>
|
||||
|
||||
<logger name="org.jboss.netty" additivity="false">
|
||||
<level value="DEBUG" />
|
||||
<appender-ref ref="console" />
|
||||
</logger>
|
||||
|
||||
|
||||
<root>
|
||||
<level value="DEBUG" />
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
</log4j:configuration>
|
||||
@@ -16,13 +16,12 @@
|
||||
|
||||
package com.navercorp.pinpoint.test.util;
|
||||
|
||||
import com.navercorp.pinpoint.profiler.util.JavaAssistUtils;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
// TODO move package
|
||||
public final class BytecodeUtils {
|
||||
@@ -63,32 +62,69 @@ public final class BytecodeUtils {
|
||||
throw new NullPointerException("className must not be null");
|
||||
}
|
||||
|
||||
final InputStream is = classLoader.getResourceAsStream(className.replace('.', '/') + ".class");
|
||||
final String jvmClassName = JavaAssistUtils.javaNameToJvmName(className);
|
||||
final InputStream is = classLoader.getResourceAsStream(jvmClassName + ".class");
|
||||
if (is == null) {
|
||||
throw new RuntimeException("No such class file: " + className);
|
||||
}
|
||||
|
||||
ReadableByteChannel channel = Channels.newChannel(is);
|
||||
ByteBuffer buffer;
|
||||
|
||||
try {
|
||||
buffer = ByteBuffer.allocate(is.available());
|
||||
|
||||
while (channel.read(buffer) >= 0) {
|
||||
if (buffer.remaining() == 0) {
|
||||
buffer.flip();
|
||||
ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity() * 2);
|
||||
newBuffer.put(buffer);
|
||||
buffer = newBuffer;
|
||||
}
|
||||
}
|
||||
return readClass(is, false);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
close(is);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.array();
|
||||
/**
|
||||
* COPY ASM method. reference : org.objectweb.asm.ClassReader
|
||||
*
|
||||
* Reads the bytecode of a class.
|
||||
*
|
||||
* @param is
|
||||
* an input stream from which to read the class.
|
||||
* @param close
|
||||
* true to close the input stream after reading.
|
||||
* @return the bytecode read from the given input stream.
|
||||
* @throws IOException
|
||||
* if a problem occurs during reading.
|
||||
*/
|
||||
public static byte[] readClass(final InputStream is, boolean close)
|
||||
throws IOException {
|
||||
if (is == null) {
|
||||
throw new IOException("Class not found");
|
||||
}
|
||||
try {
|
||||
byte[] b = new byte[is.available()];
|
||||
int len = 0;
|
||||
while (true) {
|
||||
int n = is.read(b, len, b.length - len);
|
||||
if (n == -1) {
|
||||
if (len < b.length) {
|
||||
byte[] c = new byte[len];
|
||||
System.arraycopy(b, 0, c, 0, len);
|
||||
b = c;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
len += n;
|
||||
if (len == b.length) {
|
||||
int last = is.read();
|
||||
if (last < 0) {
|
||||
return b;
|
||||
}
|
||||
byte[] c = new byte[b.length + 1000];
|
||||
System.arraycopy(b, 0, c, 0, len);
|
||||
c[len++] = (byte) last;
|
||||
b = c;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (close) {
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void close(Closeable closeable) {
|
||||
|
||||
Reference in New Issue
Block a user