我很难看出 ASM 的 LocalVariablesSorter 如何能够防止变量槽冲突的发生。变量可能来自原始源,或者我可能使用 LocalVariablesSorter.newLocal(Type t) 创建变量。稍后,visitVarInsn aise 将加入这些时段。既来自原始代码,也来 self 注入(inject)的代码。 LocalVariablesSorter 如何区分它们。它们都具有相同的槽索引,如何将其移动到正确的槽?我在现实生活中也没有看到这种情况发生。
下面是一个显示该问题的程序。它通过注入(inject)局部变量来检测 Sample.sample 方法,并在方法的开头和结尾使用它。在中间,原始源代码提供了自己的变量。正如您所看到的,ASM 为它们提供了相同的槽号,这是错误的。这就是 LocalVariablesSorter 应该停止的全部要点,但坦率地说,我不明白它是如何做到这一点的。
这是示例:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;
import org.objectweb.asm.util.CheckMethodAdapter;
public class LVSBug {
public static void main(String[] args) throws IOException {
try (InputStream is = new BufferedInputStream(LVSBug.class.getResourceAsStream("/Sample.class"))) {
ClassReader cr = new ClassReader(is);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new LVCInjector(cw);
cr.accept(cv, ClassReader.EXPAND_FRAMES);
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(System.getProperty("user.home"), "Sample.class")))) {
os.write(cw.toByteArray());
}
}
}
}
class LVCInjector extends ClassVisitor {
public LVCInjector(ClassWriter cw) {
super(Opcodes.ASM5, cw);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
if (name.equals("sample")) {
CheckMethodAdapter checker = new CheckMethodAdapter(mv);
return new LVMInjector(checker, access, desc);
} else {
return mv;
}
}
}
class LVMInjector extends LocalVariablesSorter {
private int injectedReg;
public LVMInjector(MethodVisitor mv, int access, String desc) {
super(Opcodes.ASM5, access, desc, mv);
}
@Override
public void visitCode() {
super.visitCode();
injectedReg = newLocal(Type.INT_TYPE);
super.visitLdcInsn(Integer.valueOf(1));
super.visitVarInsn(Opcodes.ISTORE, injectedReg);
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.IRETURN) {
super.visitInsn(Opcodes.POP);
super.visitVarInsn(Opcodes.ILOAD, injectedReg);
}
super.visitInsn(opcode);
}
}
class Sample {
public static int sample(String s1) {
Sample s = new Sample();
return 0;
}
}
这是检测后 Sample.sample 的 javap 输出:
public static int sample(java.lang.String);
descriptor: (Ljava/lang/String;)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: ldc #22 // int 1
2: istore_2
3: new #1 // class Sample
6: dup
7: invokespecial #16 // Method "<init>":()V
10: astore_2
11: iconst_0
12: pop
13: iload_2
14: ireturn
LocalVariableTable:
Start Length Slot Name Signature
3 12 0 s1 Ljava/lang/String;
11 4 2 s LSample;
LineNumberTable:
line 85: 3
line 86: 11
请注意,我注入(inject)的变量获得了插槽 2,但现有变量也被赋予了插槽 2,这是完全错误的。
最佳答案
好的,所以我想出了一个比 LocalvariablesSorter 假装的更简单的解决方案。我相信这个类只是在概念上被破坏了。但以下是如何轻松地将局部变量添加到现有方法中的方法。
1) 找出最后一个参数的槽号。您可以通过查看方法签名来做到这一点。
int register = ((access & Opcodes.ACC_STATIC) != 0) ? 0 : 1;
lastParmReg = register - 1;
List<String> sigs = parseSignature(desc); // my own method (see below*)
for (String sig : sigs) {
lastParmReg = register;
register += ("J".equals(sig) || "D".equals(sig)) ? 2 : 1;
}
int myNewInjectedReg = register;
register += isMyNewRegADoubleOrLong() ? 2 : 1;
....
....
2) 您知道要注入(inject)多少个局部变量,因此请使用紧邻 lastParmReg 上方的插槽作为局部变量。
3) 在 MethodVisitor 中,重写 VisitVarInsn、visitLocalVariable 和 VisitLocalVariableAnnotation 方法并执行以下操作
if the specified register slot is less than or equal to lastParmReq, just
call the super method passing the slot number as is.
if it's larger than lastParmReg, then add the number of local variables you
are injecting to this value, and pass it along to super.
这是一个 VisitVarInsn 的示例
@Override
public void visitVarInsn(int opcode, int var) {
super.visitVarInsn(opcode, (var <= lastParmReg) ? var : var + numInjectedRegs);
}
请记住,您自己注入(inject)的本地变量不会通过 MethodVisitor 的 VisitXXX 方法。您应该只对局部变量调用 super.XXXX。来自原始来源的那些是唯一通过您的 MethodVisitors 方法的。
*这是我的解析签名
private static Pattern PARM_PATTERN = Pattern.compile("(\\[*(?:[ZCBSIJFD]|(?:L[^;]+;)))");
private static List<String> parseSignature(String signature) {
List<String> parms = new ArrayList<>();
int openParenPos = signature.indexOf('(');
int closeParenPos = signature.indexOf(')', openParenPos+1);
String args = signature.substring(openParenPos + 1, closeParenPos);
if (!args.isEmpty()) {
Matcher m = PARM_PATTERN.matcher(args);
while (m.find()) {
parms.add(m.group(1));
}
}
return parms;
}
关于java - 使用 ASM 的 LocalVariablesSorter 避免变量槽冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27073721/