我正在编写一个代码质量工具。我正在扫描源代码和编译类,寻找潜在的无限循环。
我无法想象有一种方式的源代码切换语句可以无限期地循环。我说错了吗?
转换语句编译为lookupswitch
和tableswitch
操作码。出于安全考虑,我需要检查编译类,并且在质量控制程序处理编译类之前允许字节码修改。说过,是否有可能只通过修改类或用汇编程序生成类来无限循环?
我已经处理了所有其他分支说明和陈述。
你的帮助会很感激的。
编辑:结论:
正如我所怀疑的,并根据这里提供的答案,源代码中的switch语句只能向前分支,但是字节码中的任何分支指令都有可能向后跳(假设字节码修改)。
发布于 2015-10-23 05:13:09
话虽如此,通过修改类或用汇编程序生成类,是否有可能通过使用这些操作码来无限循环呢?
要有一个无限的循环,你必须回到某个地方。如果您修改了字节代码,那么在任何时候添加或更改跳转返回的地方都会发生这种情况。如果不是,它不能是一个循环,无限或其他。
发布于 2015-10-23 23:31:00
有趣的是,您可以使用字节码版本1.6 (50)来完成这一任务,但如果验证失败,则不能使用字节码版本1.7 (51)。此代码(需要ASM5)工作正常,具有无限循环:
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.*;
public class LookupTest {
public static void main(String[] args) throws InstantiationException,
IllegalAccessException, ClassNotFoundException {
new ClassLoader() {
@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
| ClassWriter.COMPUTE_FRAMES);
// Create public class extending java.lang.Object
cw.visit(V1_6, ACC_PUBLIC | ACC_SUPER, name, null,
"java/lang/Object", null);
// Create default constructor
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V",
null, null);
mv.visitCode();
// Call superclass constructor (this is required)
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>",
"()V", false);
// Create branch target
Label target = new Label();
mv.visitLabel(target);
// System.out.println("Hello");
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
mv.visitLdcInsn("Hello");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream",
"println", "(Ljava/lang/String;)V", false);
// switch(0) {
mv.visitInsn(ICONST_0);
// default: goto target;
// }
mv.visitLookupSwitchInsn(target, new int[0], new Label[0]);
mv.visitMaxs(-1, -1);
mv.visitEnd();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
}
}.loadClass("LookupGotoTest").newInstance();
}
}
但是,如果将V1_6
替换为V1_7
,则会出现以下错误:
Exception in thread "main" java.lang.VerifyError: Bad instruction
Exception Details:
Location:
LookupGotoTest.<init>()V @13: lookupswitch
Reason:
Error exists in the bytecode
Bytecode:
0x0000000: 2ab7 0008 b200 0e12 10b6 0016 03ab 0000
0x0000010: ffff fff7 0000 0000
Stackmap Table:
full_frame(@4,{Object[#2]},{})
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2658)
at java.lang.Class.getConstructor0(Class.java:3062)
at java.lang.Class.newInstance(Class.java:403)
at LookupTest.main(LookupTest.java:46)
但是,如果我选择前进跳转并添加goto指令,那么即使使用1.7字节码,它也能正常工作:
Label target2 = new Label();
// switch(0) {
mv.visitInsn(ICONST_0);
// default: goto target2;
// }
mv.visitLookupSwitchInsn(target2, new int[0], new Label[0]);
mv.visitLabel(target2);
// goto target
mv.visitJumpInsn(GOTO, target);
这种差异是由于不同的验证过程造成的:Java1.6之前的Java类没有StackMapTable,由类型推理验证,而版本1.7或更高的类由类型检查验证,类型检查对包括查找开关在内的各个指令都有单独的严格规则。
目前,我还不清楚在1.7+中是否真的不允许这样的指令,或者ASM只是生成了不正确的StackMapTable。
正如@Holger和@apangin所指出的,这可能是一个ASM缺陷,可以通过mv.visitLookupSwitchInsn(target, new int[]{1}, new Label[]{target});
添加至少一个案例分支。因此,最后:是的,您可以使用任何字节码版本在交换机中生成反向分支。
发布于 2015-10-23 07:35:15
在字节码级别,基本上一切都是gotos。表转换或查找开关指令只是要跳转到的偏移量列表。如果你想的话你可以让它向后跳。您不能让它直接跳转到自己,但这只是因为它每次从堆栈中弹出一个int。如果以int push作为前缀,则可以使用2指令循环。
https://stackoverflow.com/questions/33302816
复制