如何记录ASM中MethodVisitor访问过的常量



我试图检查在给定类中使用常量的次数。我的想法是首先使用ClassReader。accept(ClassVisitor,…)来访问类,然后使用methodVisitor . visitmethod()为每个方法声明一个methodVisitor。然后,对于每个MethodVisitor,我尝试使用visitFieldInsn。然而,这个方法从不访问常量。相反,将访问所有其他非常量变量。

下面是我的代码:

import org.objectweb.asm.*;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
public class ASMVisitor {
static class SimpleMethodVisitor extends MethodVisitor {
public String currMethodName;
public List<String[]> constants = new ArrayList<>();
private final Map<Object, Integer> constantFrequencyMap;
public SimpleMethodVisitor(int api, MethodVisitor methodVisitor, String methodName) {
super(Opcodes.ASM9, methodVisitor);
currMethodName = methodName;
constantFrequencyMap = new HashMap<>();
}
@Override
public void visitMethodInsn(int opcode, java.lang.String owner, java.lang.String name, java.lang.String descriptor, boolean isInterface) {
//            System.out.println(Opcodes.LDC);
//            System.out.println("Visited method instruction: " + opcode + ", " + owner + ", " + name + ", " + descriptor + ", " + isInterface);
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
public Map<Object, Integer> getConstantFrequencyMap() {
return constantFrequencyMap;
}
@Override
public void visitLdcInsn(Object cst) {
//            System.out.println("Method " + this.currMethodName + " contains ldc instruction: " + cst);
super.visitLdcInsn(cst);
}
@Override
public void visitInsn(int opcode) {
System.out.println("Method " + this.currMethodName + " contains instruction: " + opcode);
super.visitInsn(opcode);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
System.out.printf("FieldInsn: opcode = %s, Owner = %s, Name = %s, Descriptor = %sn",
opcode, owner, name, descriptor);
constants.add(new String[]{owner, name, descriptor});
}
}
static class SimpleClassVisitor extends ClassVisitor {
public String currClassName;
public Map<String[],Object> constants;
public Set<MethodVisitor> mvs = new HashSet<>();
public SimpleClassVisitor() {
super(Opcodes.ASM9);
currClassName = "";
this.constants = new HashMap<>();
}
@Override
public void visit(int version, int acc, String name,
String sig, String superName, String[] ifs) {
currClassName = name;
super.visit(version, acc, name, sig, superName, ifs);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
System.out.printf("visitField: Name = %s, Desc = %s, Signature = %s, Value = %sn",
name, desc, signature, value);
if (value != null && (access & Opcodes.ACC_STATIC) != 0) {
constants.put(new String[]{currClassName, name, desc}, value);
}
return super.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
//            System.out.printf("visitMethod: Name = %s, Desc = %s, Signature = %sn", name, desc, signature);
MethodVisitor methodvisitor = super.visitMethod(access, name, desc, signature, exceptions);
MethodVisitor mv = new SimpleMethodVisitor(Opcodes.ASM9, methodvisitor, name);
mvs.add(mv);
return mv;
}
}
public static void main(String[] args) throws IOException {
...
try {
byte[] bytes = Files.readAllBytes(file);
ClassReader classReader = new ClassReader(bytes);
SimpleClassVisitor scv = new SimpleClassVisitor();
classReader.accept(scv, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}

这是我的虚拟测试

package DummyTests2;
public class DummyTest1 {
public int publicIVInteger1;
public static int notFinal = -1;
public final int notStatic = -1;
public static final int dummy0 = 0;
public static final int dummy1 = -1;
public DummyTest1() {
this.publicIVInteger1 = DummyTest2.dummy1;
getPublicIVInteger1();
}
public int getPublicIVInteger1() {
setPublicIVInteger1(1);
notFinal -= dummy1;
notFinal -= dummy0;
publicIVInteger1 -= dummy1;
publicIVInteger1 -= dummy0;
return this.publicIVInteger1;
}
public void setPublicIVInteger1(int n) {
int localInt = 0;
publicIVInteger1 += dummy1;
}
}

在这种情况下,只输出非常量的变量,而不输出带有static和final的常量,如dummy0和dummy1。我还尝试使用visitInsn,似乎访问了常量,因为访问了相应的指令,但visitldinsn也从未访问过。我想知道为什么常量不能被visitFieldInsn识别,以及如何将常量的频率存储到constantFrequencyMap中。

正如有人在注释中所说,基本常量将作为字节码的文字内联,因此:

public static final int TEST_CONST = 11;
private static void bar() {
System.out.println(TEST_CONST);
}

将编译为

public static final int TEST_CONST = 11;
private static void bar() {
System.out.println(11);
}

然而,非原语常量仍然有一个GETSTATIC引用,对于原语来说,你能做的最好的事情就是事先收集所有的原语常量,然后通过比较它们的值来猜测一个文字是否引用了它们中的一个,但是这还远远不够完美。