Skip to content

Commit

Permalink
Introduce "itf" suffix to invokestatic calls
Browse files Browse the repository at this point in the history
  • Loading branch information
Earthcomputer committed Aug 26, 2022
1 parent 49462ad commit bcabb86
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 5 deletions.
3 changes: 3 additions & 0 deletions src/main/java/me/coley/recaf/parse/bytecode/Disassembler.java
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ private void visitMethodInsn(StringBuilder line, MethodInsnNode insn) {
String name = EscapeUtil.escapeCommon(insn.name);
String desc = EscapeUtil.escapeCommon(insn.desc);
line.append(' ').append(owner).append('.').append(name).append(desc);
if (insn.getOpcode() == Opcodes.INVOKESTATIC && insn.itf) {
line.append(" itf");
}
}

private void visitJumpInsn(StringBuilder line, JumpInsnNode insn) {
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/me/coley/recaf/parse/bytecode/ast/ItfAST.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.coley.recaf.parse.bytecode.ast;

public class ItfAST extends AST {
/**
* @param line Line number this node is written on.
* @param start Offset from line start this node starts at.
*/
public ItfAST(int line, int start) {
super(line, start);
}

@Override
public String print() {
return "itf";
}
}
46 changes: 43 additions & 3 deletions src/main/java/me/coley/recaf/parse/bytecode/ast/MethodInsnAST.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import me.coley.recaf.parse.bytecode.MethodCompilation;
import me.coley.recaf.parse.bytecode.exception.AssemblerException;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;

/**
Expand All @@ -13,6 +14,7 @@ public class MethodInsnAST extends InsnAST {
private final TypeAST owner;
private final NameAST name;
private final DescAST desc;
private final ItfAST itf;

/**
* @param line
Expand All @@ -29,13 +31,37 @@ public class MethodInsnAST extends InsnAST {
* Method descriptor AST.
*/
public MethodInsnAST(int line, int start, OpcodeAST opcode, TypeAST owner, NameAST name, DescAST desc) {
this(line, start, opcode, owner, name, desc, null);
}

/**
* @param line
* Line number this node is written on.
* @param start
* Offset from line start this node starts at.
* @param opcode
* Opcode AST.
* @param owner
* Method owner type AST.
* @param name
* Method name AST.
* @param desc
* Method descriptor AST.
* @param itf
* Present on invokestatic if this is an interface method ref
*/
public MethodInsnAST(int line, int start, OpcodeAST opcode, TypeAST owner, NameAST name, DescAST desc, ItfAST itf) {
super(line, start, opcode);
this.owner = owner;
this.name = name;
this.desc = desc;
this.itf = itf;
addChild(owner);
addChild(name);
addChild(desc);
if (itf != null) {
addChild(itf);
}
}

/**
Expand All @@ -59,14 +85,28 @@ public DescAST getDesc() {
return desc;
}

public ItfAST getItf() {
return itf;
}

@Override
public String print() {
return getOpcode().print() + " " + owner.print() + "." + name.print() + desc.print();
String ret = getOpcode().print() + " " + owner.print() + "." + name.print() + desc.print();
if (itf != null) {
ret += " " + itf.print();
}
return ret;
}

@Override
public void compile(MethodCompilation compilation) throws AssemblerException {
compilation.addInstruction(new MethodInsnNode(getOpcode().getOpcode(), getOwner().getType(),
getName().getName(), getDesc().getDesc()), this);
int opcode = getOpcode().getOpcode();
if (opcode == Opcodes.INVOKESTATIC) {
compilation.addInstruction(new MethodInsnNode(opcode, getOwner().getType(),
getName().getName(), getDesc().getDesc(), itf != null), this);
} else {
compilation.addInstruction(new MethodInsnNode(opcode, getOwner().getType(),
getName().getName(), getDesc().getDesc()), this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import me.coley.recaf.parse.bytecode.ast.*;
import me.coley.recaf.parse.bytecode.exception.ASTParseException;
import me.coley.recaf.util.AutoCompleteUtil;
import org.objectweb.asm.Opcodes;

import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -49,17 +50,33 @@ public MethodInsnAST visit(int lineNo, String line) throws ASTParseException {
DescParser descParser = new DescParser();
descParser.setOffset(line.indexOf('('));
DescAST desc = descParser.visit(lineNo, descS);
return new MethodInsnAST(lineNo, start, op, owner, name, desc);
// itf
ItfAST itf = null;
if (op.getOpcode() == Opcodes.INVOKESTATIC) {
if (trim.length > 2 && "itf".equals(trim[2])) {
itf = new ItfAST(lineNo, line.indexOf("itf", desc.getStart() + desc.getDesc().length()));
}
}
return new MethodInsnAST(lineNo, start, op, owner, name, desc, itf);
} catch(Exception ex) {
throw new ASTParseException(ex, lineNo, "Bad format for method instruction");
}
}

@Override
public List<String> suggest(ParseResult<RootAST> lastParse, String text) {
// METHOD owner.name+desc
// METHOD owner.name+desc [itf]
int space = text.indexOf(' ');
if (space >= 0) {
int secondSpace = text.indexOf(' ', space + 1);
if (secondSpace >= 0) {
if ("INVOKESTATIC".equals(text.substring(0, space))) {
return Collections.singletonList("itf");
} else {
return Collections.emptyList();
}
}

String sub = text.substring(space + 1);
int dot = sub.indexOf('.');
if (dot == -1)
Expand Down
33 changes: 33 additions & 0 deletions src/test/java/me/coley/recaf/AssemblyAstTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,30 @@ public void testMethodInsnArrayReturn() {
assertEquals("()[B", methodAST.getDesc().getDesc());
}

@Test
public void testMethodInsnInvokeStatic() {
String text = "INVOKESTATIC java/lang/System.exit(I)V";
MethodInsnAST methodAST = single(text);
assertEquals(text, methodAST.print());
assertEquals("INVOKESTATIC", methodAST.getOpcode().print());
assertEquals("java/lang/System", methodAST.getOwner().getType());
assertEquals("exit", methodAST.getName().getName());
assertEquals("(I)V", methodAST.getDesc().getDesc());
assertNull(methodAST.getItf());
}

@Test
public void testMethodInsnInvokeStaticItf() {
String text = "INVOKESTATIC java/util/function/Function.identity()Ljava/util/function/Function; itf";
MethodInsnAST methodAST = single(text);
assertEquals(text, methodAST.print());
assertEquals("INVOKESTATIC", methodAST.getOpcode().print());
assertEquals("java/util/function/Function", methodAST.getOwner().getType());
assertEquals("identity", methodAST.getName().getName());
assertEquals("()Ljava/util/function/Function;", methodAST.getDesc().getDesc());
assertNotNull(methodAST.getItf());
}

@Test
public void testLdcInsn() {
// int
Expand Down Expand Up @@ -512,6 +536,15 @@ public void testMemberSuggestFromMethod() {
assertTrue(suggestions.contains("println(Ljava/lang/String;)V"));
}

@Test
public void testItfSuggest() {
List<String> suggestions = suggest(null, "INVOKESTATIC java/util/function/Function.identity()Ljava/util/function/Function; i");
assertTrue(suggestions.contains("itf"));
//
suggestions = suggest(null, "INVOKEVIRTUAL java/io/PrintStream.println i");
assertFalse(suggestions.contains("itf"));
}

@Test
public void testVariableSuggestFromInsns() {
ParseResult<RootAST> ast = Parse.parse("ISTORE example\nISTORE other\nISTORE also");
Expand Down
16 changes: 16 additions & 0 deletions src/test/java/me/coley/recaf/AssemblyCasesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import me.coley.recaf.parse.bytecode.exception.VerifierException;
import me.coley.recaf.workspace.LazyClasspathResource;
import org.junit.jupiter.api.*;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.Frame;

Expand Down Expand Up @@ -687,6 +688,21 @@ public void testMergeWithOpaquePredicate() {
assertNotEquals(one, retFrameLocal.getValue());
assertEquals(two, retFrameLocal.getValue());
}

@Test
public void testInterfaceMethodRef() {
try {
MethodNode method = compile(parse(
"INVOKESTATIC java/util/function/Function.identity()Ljava/util/function/Function; itf\n" +
"POP\n" +
"RETURN"));
AbstractInsnNode invokeStaticInsn = method.instructions.get(1);
assertEquals(Opcodes.INVOKESTATIC, invokeStaticInsn.getOpcode());
assertTrue(((MethodInsnNode) invokeStaticInsn).itf, "INVOKESTATIC instruction must have itf=true");
} catch (AssemblerException ex) {
fail(ex);
}
}
}

// =============================================================== //
Expand Down

0 comments on commit bcabb86

Please sign in to comment.