我正在尝试编写单元测试以解决有关缺少stackmap帧的问题的解决方法,但为此目的,我将需要生成一个在Java 8上无法验证的类,如果它缺少stackmap帧.
下面你可以看到我的测试用例(依赖项:ASM,Guava,JUnit).它从GuineaPig类中删除了stackmap帧,希望导致其字节码无法验证.我遇到问题的部分是使用最少的代码来填充GuineaPig中的TODO,这需要堆栈图框架,以便测试通过.
import com.google.common.io.*;
import org.junit.*;
import org.junit.rules.ExpectedException;
import org.objectweb.asm.*;
import java.io.*;
import static org.objectweb.asm.Opcodes.ASM5;
public class Java6MissingStackMapFrameFixerTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
public static class GuineaPig {
public GuineaPig() {
// TODO: make me require stackmap frames
}
}
@Test
public void example_class_cannot_be_loaded_because_of_missing_stackmap_frame() throws Exception {
byte[] originalBytecode = getBytecode(GuineaPig.class);
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MethodVisitor(ASM5, super.visitMethod(access, name, desc, signature, exceptions)) {
@Override
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
// remove the stackmap frames in order to cause a VerifyError
// super.visitFrame(type, nLocal, local, nStack, stack);
}
};
}
};
new ClassReader(originalBytecode).accept(cv, 0);
byte[] transformedBytecode = cw.toByteArray();
// Files.asByteSink(new File("test.class")).write(transformedBytecode);
thrown.expect(VerifyError.class);
thrown.expectMessage("Expecting a stackmap frame");
Class<?> clazz = new TestingClassLoader().defineClass(transformedBytecode);
clazz.newInstance();
}
private static byte[] getBytecode(Class<?> clazz) throws IOException {
String classFile = clazz.getName().replace(".", "/") + ".class";
try (InputStream b = clazz.getClassLoader().getResourceAsStream(classFile)) {
return ByteStreams.toByteArray(b);
}
}
private static class TestingClassLoader extends ClassLoader {
public Class<?> defineClass(byte[] bytecode) {
ClassReader cr = new ClassReader(bytecode);
String className = cr.getClassName().replace("/", ".");
return this.defineClass(className, bytecode, 0, bytecode.length);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Esk*_*ola 11
Java VM规范§4.10.1(按类型检查验证)指定何时需要堆栈映射帧.首先,它给出了一个非正式的描述:
目的是堆栈映射帧必须出现在方法中每个基本块的开头.堆栈映射帧指定每个操作数堆栈条目的验证类型以及每个基本块开始时的每个局部变量的验证类型.
详细规范在§4.10.1.6(带代码的类型检查方法)中给出.goto命令需要堆栈映射帧:
在没有为其提供堆栈映射帧的情况下在无条件分支之后使用代码是非法的.
和所有其他分支命令:
如果目标具有关联的堆栈帧,则分支到目标是类型安全的,并且当前堆栈帧StackFrame可分配给Frame.
此外,异常处理程序的开头需要一个堆栈映射框架:
如果指令的传出类型状态是ExcStackFrame,则指令满足异常处理程序,并且处理程序的目标(处理程序代码的初始指令)是类型安全的,假定传入类型状态T.
最后,§4.10.1.9(类型检查指令)指定哪些指令需要具有堆栈映射帧的分支目标.寻找targetIsTypeSafe类型规则; 说明书goto,if*,lookupswitch和tableswitch拥有它.
即使以下代码也需要stackmap框架:
public static class GuineaPig {
public GuineaPig() {
int i = 1;
if (i > 0) {
// code branch to require stackmap frames
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果它们丢失,代码将失败并出现异常:
java.lang.VerifyError: Expecting a stackmap frame at branch target 10
Exception Details:
Location:
net/orfjackal/retrolambda/Java6MissingStackMapFrameFixerTest$GuineaPig.<init>()V @7: ifle
Reason:
Expected stackmap frame at this location.
Bytecode:
0000000: 2ab7 000c 043c 1b9e 0003 b1
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2658)
at java.lang.Class.getConstructor0(Class.java:2964)
at java.lang.Class.newInstance(Class.java:403)
Run Code Online (Sandbox Code Playgroud)
这是字节码:
public net.orfjackal.retrolambda.Java6MissingStackMapFrameFixerTest$GuineaPig();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: iconst_1
5: istore_1
6: iload_1
7: ifle 10
10: return
LineNumberTable:
line 22: 0
line 23: 4
line 24: 6
line 27: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lnet/orfjackal/retrolambda/Java6MissingStackMapFrameFixerTest$GuineaPig;
6 5 1 i I
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 10
locals = [ class net/orfjackal/retrolambda/Java6MissingStackMapFrameFixerTest$GuineaPig, int ]
stack = []
Run Code Online (Sandbox Code Playgroud)
PS花了我一些时间来解决这个问题,因为默认情况下我使用代码覆盖率运行我的单元测试,而IDEA的代码覆盖工具显然会自动重新计算所有类的stackmap帧,从而解除了我的测试删除它们的努力.
| 归档时间: |
|
| 查看次数: |
2655 次 |
| 最近记录: |