Skip to content

Commit

Permalink
Support NEW, DUP, GOTO and labels
Browse files Browse the repository at this point in the history
  • Loading branch information
roscopeco committed Apr 30, 2022
1 parent c50ad9e commit 7c98dda
Show file tree
Hide file tree
Showing 16 changed files with 444 additions and 87 deletions.
37 changes: 32 additions & 5 deletions src/main/antlr/com/roscopeco/jasm/antlr/Jasm.g4
Expand Up @@ -9,7 +9,7 @@ class
;

classname
: TYPENAME
: QNAME
| NAME
;

Expand All @@ -27,7 +27,7 @@ method
;

membername
: TYPENAME
: QNAME
| NAME
| INIT
| CLINIT
Expand All @@ -40,7 +40,7 @@ type
| TYPE_FLOAT
| TYPE_DOUBLE
| TYPE_BOOL
| TYPENAME SEMI
| QNAME SEMI
;

modifier
Expand All @@ -65,15 +65,19 @@ instruction
: insn_aconstnull
| insn_aload
| insn_areturn
| insn_dup
| insn_freturn
| insn_goto
| insn_iconst
| insn_invokeinterface
| insn_invokespecial
| insn_invokestatic
| insn_invokevirtual
| insn_ireturn
| insn_ldc
| insn_new
| insn_return
| label
;

insn_aconstnull
Expand All @@ -88,10 +92,18 @@ insn_areturn
: ARETURN
;

insn_dup
: DUP
;

insn_freturn
: FRETURN
;

insn_goto
: GOTO NAME
;

insn_iconst
: ICONST atom
;
Expand All @@ -112,8 +124,12 @@ insn_invokevirtual
: INVOKEVIRTUAL owner DOT membername method_descriptor
;

label
: LABEL
;

owner
: TYPENAME
: QNAME
| NAME
;

Expand All @@ -129,6 +145,10 @@ insn_ldc
: LDC atom
;

insn_new
: NEW QNAME
;

insn_return
: RETURN
;
Expand Down Expand Up @@ -166,14 +186,17 @@ CLINIT: '<clinit>';
ACONST_NULL: 'aconstnull';
ALOAD: 'aload';
ARETURN: 'areturn';
DUP: 'dup';
FRETURN: 'freturn';
GOTO: 'goto';
ICONST: 'iconst';
INVOKEINTERFACE: 'invokeinterface';
INVOKESPECIAL: 'invokespecial';
INVOKESTATIC: 'invokestatic';
INVOKEVIRTUAL: 'invokevirtual';
IRETURN: 'ireturn';
LDC: 'ldc';
NEW: 'new';
RETURN: 'return';

TYPE_VOID: 'V';
Expand All @@ -186,11 +209,15 @@ TYPE_BOOL: 'Z';
TRUE : 'true';
FALSE : 'false';

LABEL
: [a-zA-Z_] [a-zA-Z_0-9]* ':'
;

NAME
: [a-zA-Z_] [a-zA-Z_0-9]*
;

TYPENAME
QNAME
: [a-zA-Z_] [a-zA-Z_0-9/]*
;

Expand Down
71 changes: 70 additions & 1 deletion src/main/java/com/roscopeco/jasm/JasmAssemblingVisitor.java
Expand Up @@ -8,16 +8,23 @@
import com.roscopeco.jasm.antlr.JasmBaseVisitor;
import com.roscopeco.jasm.antlr.JasmParser;
import lombok.NonNull;
import lombok.val;
import org.antlr.v4.runtime.RuleContext;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.FRETURN;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.ICONST_0;
import static org.objectweb.asm.Opcodes.ICONST_1;
import static org.objectweb.asm.Opcodes.ICONST_2;
Expand All @@ -30,6 +37,7 @@
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V17;

Expand Down Expand Up @@ -82,6 +90,7 @@ public Void visitMethod(final JasmParser.MethodContext ctx) {
}

private class JasmMethodVisitor extends JasmBaseVisitor<Void> {
private final HashMap<String, LabelHolder> labels = new HashMap<>();
private MethodVisitor methodVisitor;

@Override
Expand All @@ -99,9 +108,18 @@ public Void visitMethod(final JasmParser.MethodContext ctx) {
this.methodVisitor.visitMaxs(0, 0);
this.methodVisitor.visitEnd();

guardAllLabelsDeclared();

return ret;
}

@Override
public Void visitLabel(JasmParser.LabelContext ctx) {
final var label = declareLabel(ctx.LABEL().getText());
this.methodVisitor.visitLabel(label.label);
return super.visitLabel(ctx);
}

@Override
public Void visitInsn_aconstnull(JasmParser.Insn_aconstnullContext ctx) {
this.methodVisitor.visitInsn(ACONST_NULL);
Expand All @@ -120,12 +138,24 @@ public Void visitInsn_areturn(JasmParser.Insn_areturnContext ctx) {
return super.visitInsn_areturn(ctx);
}

@Override
public Void visitInsn_dup(JasmParser.Insn_dupContext ctx) {
this.methodVisitor.visitInsn(DUP);
return super.visitInsn_dup(ctx);
}

@Override
public Void visitInsn_freturn(JasmParser.Insn_freturnContext ctx) {
this.methodVisitor.visitInsn(FRETURN);
return super.visitInsn_freturn(ctx);
}

@Override
public Void visitInsn_goto(JasmParser.Insn_gotoContext ctx) {
this.methodVisitor.visitJumpInsn(GOTO, getLabel(ctx.NAME().getText()).label);
return super.visitInsn_goto(ctx);
}

@Override
public Void visitInsn_iconst(final JasmParser.Insn_iconstContext ctx) {
this.methodVisitor.visitInsn(generateIconstOpcode(ctx.atom()));
Expand Down Expand Up @@ -206,6 +236,12 @@ public Void visitInsn_ldc(JasmParser.Insn_ldcContext ctx) {
return super.visitInsn_ldc(ctx);
}

@Override
public Void visitInsn_new(JasmParser.Insn_newContext ctx) {
this.methodVisitor.visitTypeInsn(NEW, ctx.QNAME().getText());
return super.visitInsn_new(ctx);
}

@Override
public Void visitInsn_return(final JasmParser.Insn_returnContext ctx) {
this.methodVisitor.visitInsn(RETURN);
Expand Down Expand Up @@ -259,8 +295,41 @@ private String unescapeConstantString(@NonNull final String constant) {
return constant.substring(1, constant.length() - 1).replace("\"\"", "\"");
}

private String fixDescriptor(final String languageDescriptor) {
private String fixDescriptor(@NonNull final String languageDescriptor) {
return languageDescriptor.replaceAll("([IJFDZV]);", "$1");
}

private String normaliseLabelName(@NonNull final String labelName) {
if (labelName.endsWith(":")) {
return labelName.substring(0, labelName.length() - 1);
} else {
return labelName;
}
}

private LabelHolder getLabel(@NonNull final String name) {
return labels.computeIfAbsent(normaliseLabelName(name), k -> new LabelHolder(new Label(), false));
}

private LabelHolder declareLabel(@NonNull final String name) {
final var normalName = normaliseLabelName(name);
final var label = labels.get(normalName);

return Optional.ofNullable(label)
.orElse(labels.put(normalName, new LabelHolder(new Label(), true)));
}

private void guardAllLabelsDeclared() {
final var undeclaredLabels = labels.entrySet().stream()
.filter(l -> !l.getValue().declared())
.map(Map.Entry::getKey)
.collect(Collectors.joining());

if (!undeclaredLabels.isEmpty()) {
throw new SyntaxErrorException("Labels used but not declared: [" + undeclaredLabels + "]");
}
}

private record LabelHolder(Label label, boolean declared) { }
}
}
66 changes: 65 additions & 1 deletion src/test/java/com/roscopeco/jasm/JasmIntegrationTests.java
Expand Up @@ -10,13 +10,16 @@
import java.util.stream.Stream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.objectweb.asm.util.TraceClassVisitor;

import static com.roscopeco.jasm.TestUtil.doParse;
import static com.roscopeco.jasm.TestUtil.testCaseParser;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class JasmIntegrationTests {

Expand Down Expand Up @@ -331,6 +334,59 @@ public class com/roscopeco/jasm/InvokeTests {
MAXSTACK = 0
MAXLOCALS = 0
}
"""),


/* ************************************************************************************************ */
Arguments.of("com/roscopeco/jasm/insntest/Dup.jasm", """
// class version 61.0 (61)
// access flags 0x0
class com/roscopeco/jasm/insntest/Dup {
// access flags 0x0
insnTest()V
DUP
MAXSTACK = 0
MAXLOCALS = 0
}
"""),


/* ************************************************************************************************ */
Arguments.of("com/roscopeco/jasm/insntest/New.jasm", """
// class version 61.0 (61)
// access flags 0x0
class com/roscopeco/jasm/insntest/New {
// access flags 0x0
insnTest()V
NEW java/util/ArrayList
MAXSTACK = 0
MAXLOCALS = 0
}
"""),


/* ************************************************************************************************ */
Arguments.of("com/roscopeco/jasm/GotoLabelTest.jasm", """
// class version 61.0 (61)
// access flags 0x1
public class com/roscopeco/jasm/GotoLabelTest {
// access flags 0x9
public static testMethod(Ljava/util/List;)V
GOTO L0
ALOAD 0
LDC "CANARY"
INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z (itf)
L0
RETURN
MAXSTACK = 0
MAXLOCALS = 0
}
""")
);
}
Expand All @@ -343,8 +399,16 @@ void setup() {

@ParameterizedTest
@MethodSource("provideTestCases")
void shouldAssembleEmptyClass(final String testCaseSource, final String expectedTraceClassOutput) {
void traceClassOutputShouldBeAsExpected(final String testCaseSource, final String expectedTraceClassOutput) {
testCaseParser(testCaseSource).class_().accept(assembler);
assertThat(baos).hasToString(expectedTraceClassOutput);
}

@Test
void assemblyShouldFailOnUndeclaredLabel() {
final var parsed = testCaseParser("com/roscopeco/jasm/ClassWithUndeclaredLabel.jasm").class_();
assertThatThrownBy(() -> parsed.accept(assembler))
.isInstanceOf(SyntaxErrorException.class)
.hasMessageContaining("undeclaredLabel");
}
}

0 comments on commit 7c98dda

Please sign in to comment.