传入 ClassWriter 时 ClassNode.accept() 出现 IllegalArgumentException
IllegalArgumentException on ClassNode.accept() when passing in ClassWriter
我对 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 的 Visitor API 来完成。 包含一个 reader 和 writer 链接的示例,它需要更少的内存并且可能比使用树 API.
更快
我对 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 的 Visitor API 来完成。