问题描述
我对 ClassNode 所做的实际更改似乎已经奏效(据我所知),但是当我开始编写文件时,在下面指定的行上发生了 IllegalArgumentException。这没有多大意义,因为 ClassNode.accept() 接受一个 ClassVisitor 实例,而 ClassWriter 扩展了 ClassVisitor,如果有人对 asm 有更多经验,我们将不胜感激。
FileInputStream stream = new FileInputStream(filePath);
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(stream);
classReader.accept(classNode,0);
for (MethodNode methodNode : classNode.methods){
for (AbstractInsnNode abstractInsnNode : methodNode.instructions.toArray()){
if (abstractInsnNode.getopcode() == Opcodes.LDC){
LdcInsnNode ldc = (LdcInsnNode) abstractInsnNode;
if (ldc.cst.toString().equals("[a-zA-Z0-9_]+")){
ldc.cst = Pattern.compile("");
}
}
}
}
ClassWriter classWriter = new ClassWriter(ClassWriter.COmpuTE_MAXS);
classNode.accept(classWriter); //Error Ocurs on this line
FileOutputStream outputStream = new FileOutputStream(filePath);
outputStream.write(classWriter.toByteArray());
outputStream.close();
完整的错误信息是:
Exception in thread "main" java.lang.IllegalArgumentException: value
at org.objectweb.asm.SymbolTable.addConstant(SymbolTable.java:501)
at org.objectweb.asm.MethodWriter.visitLdcInsn(MethodWriter.java:1290)
at org.objectweb.asm.tree.LdcInsnNode.accept(LdcInsnNode.java:66)
at org.objectweb.asm.tree.InsnList.accept(InsnList.java:144)
at org.objectweb.asm.tree.MethodNode.accept(MethodNode.java:792)
at org.objectweb.asm.tree.MethodNode.accept(MethodNode.java:690)
at org.objectweb.asm.tree.ClassNode.accept(ClassNode.java:426)
at dev.bodner.jack.Main.main(Main.java:105)
我使用的是 java 16 和 asm 9.2
解决方法
当你想要检测像 Pattern.compile("[a-zA-Z0-9_]+")
这样的语句时,你必须意识到它会被编译成两条指令
ldc "[a-zA-Z0-9_]+" // pushing the string constant to the stack
invokestatic java/util/regex/Pattern compile (Ljava/lang/String;)Ljava/util/regex/Pattern;
因此,您只需更改由 ldc
指令推送的字符串。
例如
ClassNode classNode = new ClassNode();
try(FileInputStream stream = new FileInputStream(filePath)) {
ClassReader classReader = new ClassReader(stream);
classReader.accept(classNode,0);
}
for (MethodNode methodNode: classNode.methods) {
for (AbstractInsnNode abstractInsnNode: methodNode.instructions) {
if (abstractInsnNode.getOpcode() == Opcodes.LDC) {
LdcInsnNode ldc = (LdcInsnNode) abstractInsnNode;
if (ldc.cst.equals("[a-zA-Z0-9_]+")) {
ldc.cst = ""; // the replacement string
}
}
}
}
ClassWriter classWriter = new ClassWriter(0);
classNode.accept(classWriter);
try(FileOutputStream outputStream = new FileOutputStream(filePath)) {
outputStream.write(classWriter.toByteArray());
}
这将替换任何出现的指令 ldc "[a-zA-Z0-9_]+"
,无论字符串如何使用。这对于您的用例来说可能已经足够了,但是如果您想确保您只是更改了立即传递给 Pattern.compile
的字符串,您可以将循环体更改为
if (node.getOpcode() == Opcodes.LDC && isPatternCompile(node.getNext())) {
LdcInsnNode ldc = (LdcInsnNode) node;
if (ldc.cst.equals("[a-zA-Z0-9_]+")) {
ldc.cst = ""; // the replacement string
}
}
介绍以下辅助方法:
private static boolean isPatternCompile(AbstractInsnNode n) {
String expectedDesc;
if(n.getOpcode() == Opcodes.INVOKESTATIC) {
expectedDesc = "(Ljava/lang/String;)Ljava/util/regex/Pattern;";
}
else { // check for Pattern.compile(String,int) with int constant
switch(n.getOpcode()) {
default: return false;
case Opcodes.LDC:
if(!((((LdcInsnNode)n).cst) instanceof Integer)) return false;
case Opcodes.ICONST_0: case Opcodes.ICONST_1: case Opcodes.ICONST_2:
case Opcodes.ICONST_3: case Opcodes.ICONST_4: case Opcodes.ICONST_M1:
case Opcodes.BIPUSH: case Opcodes.SIPUSH:
}
n = n.getNext();
if(n.getOpcode() != Opcodes.INVOKESTATIC) return false;
expectedDesc = "(Ljava/lang/String;I)Ljava/util/regex/Pattern;";
}
MethodInsnNode m = (MethodInsnNode) n;
return m.name.equals("compile")
&& m.owner.equals("java/util/regex/Pattern")
&& m.desc.equals(expectedDesc);
}
请注意,当您决定只替换所有出现的特定字符串常量时,即无需检查后续说明,您可以直接使用 ASM 的访问者 API 来完成。 This answer 包含这样一个读取器和写入器链接的示例,它需要更少的内存,而且可能比使用 Tree API 更快。