Add CF frontend for testing
- Add InternalOptions.enableCfFrontend to enable CF frontend for testing
- Add InternalOptions.skipIR to skip IRConverter for testing
- Add CfFrontendExamplesTest to test CF frontend without involving IR
- Add CfExtended to handle SWAP and DUP[2][_X1|_X2]
- Add CfIinc to handle IINC
- Add CfTryCatch constructor accepting lists of guards and targets
- Add InternalOptions.DETERMINISTIC_DEBUGGING for testing
Change-Id: Iae03784aabb3f7b48da712902dfb122185c4a4c3
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 181ebac..8763e64 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfExtended;
import com.android.tools.r8.cf.code.CfFieldInstruction;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfFrame.Uninitialized;
@@ -22,6 +23,7 @@
import com.android.tools.r8.cf.code.CfGoto;
import com.android.tools.r8.cf.code.CfIf;
import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfIinc;
import com.android.tools.r8.cf.code.CfInstanceOf;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
@@ -152,6 +154,13 @@
print("nop");
}
+ public void print(CfExtended instruction) {
+ switch (instruction.getOpcode()) {
+ default:
+ print("???");
+ }
+ }
+
public void print(CfThrow insn) {
print("athrow");
}
@@ -401,6 +410,15 @@
printPrefixed(store.getType(), "store", store.getLocalIndex());
}
+ public void print(CfIinc instruction) {
+ indent();
+ builder
+ .append("iinc ")
+ .append(instruction.getLocalIndex())
+ .append(' ')
+ .append(instruction.getIncrement());
+ }
+
private void printPrefixed(ValueType type, String instruction, int local) {
indent();
builder.append(typePrefix(type)).append(instruction).append(' ').append(local);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfExtended.java b/src/main/java/com/android/tools/r8/cf/code/CfExtended.java
new file mode 100644
index 0000000..c75f0e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfExtended.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfExtended extends CfInstruction {
+
+ private final int opcode;
+
+ public CfExtended(int opcode) {
+ this.opcode = opcode;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor, NamingLens lens) {
+ visitor.visitInsn(opcode);
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ public int getOpcode() {
+ return opcode;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfIinc.java b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
new file mode 100644
index 0000000..6db7250
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfIinc.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf.code;
+
+import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+
+public class CfIinc extends CfInstruction {
+
+ private final int var;
+ private final int increment;
+
+ public CfIinc(int var, int increment) {
+ this.var = var;
+ this.increment = increment;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor, NamingLens lens) {
+ visitor.visitIincInsn(var, increment);
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ public int getLocalIndex() {
+ return var;
+ }
+
+ public int getIncrement() {
+ return increment;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
index a0d1f6a..6dceda0 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfTryCatch.java
@@ -16,17 +16,23 @@
public final List<DexType> guards;
public final List<CfLabel> targets;
- public CfTryCatch(
+ public CfTryCatch(CfLabel start, CfLabel end, List<DexType> guards, List<CfLabel> targets) {
+ this.start = start;
+ this.end = end;
+ this.guards = guards;
+ this.targets = targets;
+ }
+
+ public static CfTryCatch fromBuilder(
CfLabel start,
CfLabel end,
CatchHandlers<BasicBlock> handlers,
CfBuilder builder) {
- this.start = start;
- this.end = end;
- guards = handlers.getGuards();
- targets = new ArrayList<>(handlers.getAllTargets().size());
+ List<DexType> guards = handlers.getGuards();
+ ArrayList<CfLabel> targets = new ArrayList<>(handlers.getAllTargets().size());
for (BasicBlock block : handlers.getAllTargets()) {
targets.add(builder.getLabel(block));
}
+ return new CfTryCatch(start, end, guards, targets);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 61cd5a7..ab1eca5 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -45,6 +45,11 @@
this.start = start;
}
+ public LocalVariableInfo(int index, DebugLocalInfo local, CfLabel start, CfLabel end) {
+ this(index, local, start);
+ setEnd(end);
+ }
+
public void setEnd(CfLabel end) {
assert this.end == null;
assert end != null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index 20949c3..b5a3fc2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ProgramClassCollection;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableSet;
@@ -66,7 +67,9 @@
// code, but this non-determinism exists even with the same order of classes since we
// may process classes concurrently and fail-fast on the first error.
private <T> boolean reorderClasses(List<T> classes) {
- Collections.shuffle(classes);
+ if (!InternalOptions.DETERMINISTIC_DEBUGGING) {
+ Collections.shuffle(classes);
+ }
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 32cc10b..b18e916 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -3,14 +3,58 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static org.objectweb.asm.ClassReader.EXPAND_FRAMES;
import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
import static org.objectweb.asm.Opcodes.ACC_DEPRECATED;
import static org.objectweb.asm.Opcodes.ASM6;
import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.cf.code.CfArrayLength;
+import com.android.tools.r8.cf.code.CfArrayLoad;
+import com.android.tools.r8.cf.code.CfArrayStore;
+import com.android.tools.r8.cf.code.CfBinop;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfConstClass;
+import com.android.tools.r8.cf.code.CfConstMethodHandle;
+import com.android.tools.r8.cf.code.CfConstMethodType;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfExtended;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.Uninitialized;
+import com.android.tools.r8.cf.code.CfFrame.UninitializedNew;
+import com.android.tools.r8.cf.code.CfFrame.UninitializedThis;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
+import com.android.tools.r8.cf.code.CfIinc;
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfMonitor;
+import com.android.tools.r8.cf.code.CfMultiANewArray;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
+import com.android.tools.r8.cf.code.CfNop;
+import com.android.tools.r8.cf.code.CfPop;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStore;
+import com.android.tools.r8.cf.code.CfSwitch;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.cf.code.CfUnop;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
import com.android.tools.r8.graph.DexValue.DexValueArray;
import com.android.tools.r8.graph.DexValue.DexValueBoolean;
@@ -26,12 +70,22 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.JarCode.ReparseContext;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.Monitor;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -40,8 +94,10 @@
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;
@@ -64,8 +120,12 @@
public void read(Origin origin, ClassKind classKind, InputStream input) throws IOException {
ClassReader reader = new ClassReader(input);
- reader.accept(new CreateDexClassVisitor(
- origin, classKind, reader.b, application, classConsumer), SKIP_FRAMES);
+ int flags = SKIP_FRAMES;
+ if (application.options.enableCfFrontend) {
+ flags = EXPAND_FRAMES;
+ }
+ reader.accept(
+ new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer), flags);
}
private static int cleanAccessFlags(int access) {
@@ -210,6 +270,9 @@
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
+ if (application.options.enableCfFrontend) {
+ return new CfCreateMethodVisitor(access, name, desc, signature, exceptions, this);
+ }
return new CreateMethodVisitor(access, name, desc, signature, exceptions, this);
}
@@ -396,7 +459,7 @@
private final int access;
private final String name;
private final String desc;
- private final CreateDexClassVisitor parent;
+ final CreateDexClassVisitor parent;
private final int parameterCount;
private List<DexAnnotation> annotations = null;
private DexValue defaultAnnotation = null;
@@ -404,6 +467,9 @@
private List<List<DexAnnotation>> parameterAnnotations = null;
private List<DexValue> parameterNames = null;
private List<DexValue> parameterFlags = null;
+ final DexMethod method;
+ final MethodAccessFlags flags;
+ Code code = null;
public CreateMethodVisitor(int access, String name, String desc, String signature,
String[] exceptions, CreateDexClassVisitor parent) {
@@ -412,6 +478,8 @@
this.name = name;
this.desc = desc;
this.parent = parent;
+ this.method = parent.application.getMethod(parent.type, name, desc);
+ this.flags = createMethodAccessFlags(name, access);
parameterCount = JarApplicationReader.getArgumentCount(desc);
if (exceptions != null && exceptions.length > 0) {
DexValue[] values = new DexValue[exceptions.length];
@@ -511,15 +579,17 @@
}
@Override
- public void visitEnd() {
- DexMethod method = parent.application.getMethod(parent.type, name, desc);
- MethodAccessFlags flags = createMethodAccessFlags(name, access);
- Code code = null;
- if (!flags.isAbstract()
- && !flags.isNative()
- && parent.classKind == ClassKind.PROGRAM) {
+ public void visitCode() {
+ assert !flags.isAbstract() && !flags.isNative();
+ if (parent.classKind == ClassKind.PROGRAM) {
code = new JarCode(method, parent.origin, parent.context, parent.application);
}
+ }
+
+ @Override
+ public void visitEnd() {
+ assert flags.isAbstract() || flags.isNative() || parent.classKind != ClassKind.PROGRAM
+ || code != null;
DexAnnotationSetRefList parameterAnnotationSets;
if (parameterAnnotations == null) {
parameterAnnotationSets = DexAnnotationSetRefList.empty();
@@ -566,6 +636,651 @@
}
}
+ private static class CfCreateMethodVisitor extends CreateMethodVisitor {
+
+ private final JarApplicationReader application;
+ private final DexItemFactory factory;
+ private int maxStack;
+ private int maxLocals;
+ private List<CfInstruction> instructions;
+ private List<CfTryCatch> tryCatchRanges;
+ private List<LocalVariableInfo> localVariables;
+ private Map<Label, CfLabel> labelMap;
+
+ public CfCreateMethodVisitor(
+ int access,
+ String name,
+ String desc,
+ String signature,
+ String[] exceptions,
+ CreateDexClassVisitor parent) {
+ super(access, name, desc, signature, exceptions, parent);
+ this.application = parent.application;
+ this.factory = application.getFactory();
+ }
+
+ @Override
+ public void visitCode() {
+ assert !flags.isAbstract() && !flags.isNative();
+ maxStack = 0;
+ maxLocals = 0;
+ instructions = new ArrayList<>();
+ tryCatchRanges = new ArrayList<>();
+ localVariables = new ArrayList<>();
+ labelMap = new IdentityHashMap<>();
+ }
+
+ @Override
+ public void visitEnd() {
+ if (!flags.isAbstract() && !flags.isNative() && parent.classKind == ClassKind.PROGRAM) {
+ code =
+ new CfCode(method, maxStack, maxLocals, instructions, tryCatchRanges, localVariables);
+ }
+ super.visitEnd();
+ }
+
+ @Override
+ public void visitFrame(
+ int type, int nLocal, Object[] localTypes, int nStack, Object[] stackTypes) {
+ Int2ReferenceSortedMap<DexType> dexLocals = new Int2ReferenceAVLTreeMap<>();
+ Int2ReferenceSortedMap<Uninitialized> allocators = new Int2ReferenceAVLTreeMap<>();
+ parseLocals(nLocal, localTypes, dexLocals, allocators);
+ List<DexType> dexStack = parseStack(nStack, stackTypes);
+ instructions.add(new CfFrame(dexLocals, allocators, dexStack));
+ }
+
+ private void parseLocals(
+ int nLocal,
+ Object[] localTypes,
+ Int2ReferenceSortedMap<DexType> dexLocals,
+ Int2ReferenceSortedMap<Uninitialized> allocators) {
+ int i = 0;
+ for (int j = 0; j < nLocal; j++) {
+ Object localType = localTypes[j];
+ if (localType instanceof Label) {
+ CfLabel cfLabel = getLabel((Label) localType);
+ allocators.put(i++, new UninitializedNew(cfLabel));
+ } else if (localType == Opcodes.UNINITIALIZED_THIS) {
+ allocators.put(i++, new UninitializedThis());
+ } else {
+ DexType dexType = parseAsmType(localType);
+ dexLocals.put(i++, dexType);
+ if (dexType == factory.longType || dexType == factory.doubleType) {
+ i++;
+ }
+ }
+ }
+ }
+
+ private CfLabel getLabel(Label label) {
+ return labelMap.computeIfAbsent(label, l -> new CfLabel());
+ }
+
+ private List<DexType> parseStack(int nStack, Object[] stackTypes) {
+ List<DexType> dexStack = new ArrayList<>(nStack);
+ for (int i = 0; i < nStack; i++) {
+ Object stackType = stackTypes[i];
+ if (stackType instanceof Label || stackType == Opcodes.UNINITIALIZED_THIS) {
+ throw new Unimplemented("Uninitialized values not supported in stack slots");
+ }
+ DexType dexType = parseAsmType(stackType);
+ dexStack.add(dexType);
+ }
+ return dexStack;
+ }
+
+ private DexType parseAsmType(Object local) {
+ if (local == null || local == Opcodes.TOP) {
+ return null;
+ } else if (local == Opcodes.INTEGER) {
+ return factory.intType;
+ } else if (local == Opcodes.FLOAT) {
+ return factory.floatType;
+ } else if (local == Opcodes.LONG) {
+ return factory.longType;
+ } else if (local == Opcodes.DOUBLE) {
+ return factory.doubleType;
+ } else if (local == Opcodes.NULL) {
+ return DexItemFactory.nullValueType;
+ } else if (local instanceof String) {
+ return createTypeFromInternalType((String) local);
+ } else {
+ throw new Unreachable("Unexpected ASM type: " + local);
+ }
+ }
+
+ private DexType createTypeFromInternalType(String local) {
+ assert local.indexOf('.') == -1;
+ return factory.createType("L" + local + ";");
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ switch (opcode) {
+ case Opcodes.NOP:
+ instructions.add(new CfNop());
+ break;
+ case Opcodes.ACONST_NULL:
+ instructions.add(new CfConstNull());
+ break;
+ case Opcodes.ICONST_M1:
+ case Opcodes.ICONST_0:
+ case Opcodes.ICONST_1:
+ case Opcodes.ICONST_2:
+ case Opcodes.ICONST_3:
+ case Opcodes.ICONST_4:
+ case Opcodes.ICONST_5:
+ instructions.add(new CfConstNumber(opcode - Opcodes.ICONST_0, ValueType.INT));
+ break;
+ case Opcodes.LCONST_0:
+ case Opcodes.LCONST_1:
+ instructions.add(new CfConstNumber(opcode - Opcodes.LCONST_0, ValueType.LONG));
+ break;
+ case Opcodes.FCONST_0:
+ case Opcodes.FCONST_1:
+ case Opcodes.FCONST_2:
+ instructions.add(
+ new CfConstNumber(
+ Float.floatToRawIntBits(opcode - Opcodes.FCONST_0), ValueType.FLOAT));
+ break;
+ case Opcodes.DCONST_0:
+ case Opcodes.DCONST_1:
+ instructions.add(
+ new CfConstNumber(
+ Double.doubleToRawLongBits(opcode - Opcodes.DCONST_0), ValueType.DOUBLE));
+ break;
+ case Opcodes.IALOAD:
+ case Opcodes.LALOAD:
+ case Opcodes.FALOAD:
+ case Opcodes.DALOAD:
+ case Opcodes.AALOAD:
+ case Opcodes.BALOAD:
+ case Opcodes.CALOAD:
+ case Opcodes.SALOAD:
+ instructions.add(new CfArrayLoad(getMemberTypeForOpcode(opcode)));
+ break;
+ case Opcodes.IASTORE:
+ case Opcodes.LASTORE:
+ case Opcodes.FASTORE:
+ case Opcodes.DASTORE:
+ case Opcodes.AASTORE:
+ case Opcodes.BASTORE:
+ case Opcodes.CASTORE:
+ case Opcodes.SASTORE:
+ instructions.add(new CfArrayStore(getMemberTypeForOpcode(opcode)));
+ break;
+ case Opcodes.POP:
+ instructions.add(new CfPop(ValueType.INT_OR_FLOAT_OR_NULL));
+ break;
+ case Opcodes.POP2:
+ instructions.add(new CfPop(ValueType.LONG_OR_DOUBLE));
+ break;
+ case Opcodes.DUP:
+ case Opcodes.DUP_X1:
+ case Opcodes.DUP_X2:
+ case Opcodes.DUP2:
+ case Opcodes.DUP2_X1:
+ case Opcodes.DUP2_X2:
+ case Opcodes.SWAP:
+ // TODO(mathiasr): Merge with POP/POP2; rename into CfStackInstruction with enum field.
+ instructions.add(new CfExtended(opcode));
+ break;
+ case Opcodes.IADD:
+ case Opcodes.LADD:
+ case Opcodes.FADD:
+ case Opcodes.DADD:
+ case Opcodes.ISUB:
+ case Opcodes.LSUB:
+ case Opcodes.FSUB:
+ case Opcodes.DSUB:
+ case Opcodes.IMUL:
+ case Opcodes.LMUL:
+ case Opcodes.FMUL:
+ case Opcodes.DMUL:
+ case Opcodes.IDIV:
+ case Opcodes.LDIV:
+ case Opcodes.FDIV:
+ case Opcodes.DDIV:
+ case Opcodes.IREM:
+ case Opcodes.LREM:
+ case Opcodes.FREM:
+ case Opcodes.DREM:
+ instructions.add(new CfBinop(opcode));
+ break;
+ case Opcodes.INEG:
+ case Opcodes.LNEG:
+ case Opcodes.FNEG:
+ case Opcodes.DNEG:
+ instructions.add(new CfUnop(opcode));
+ break;
+ case Opcodes.ISHL:
+ case Opcodes.LSHL:
+ case Opcodes.ISHR:
+ case Opcodes.LSHR:
+ case Opcodes.IUSHR:
+ case Opcodes.LUSHR:
+ case Opcodes.IAND:
+ case Opcodes.LAND:
+ case Opcodes.IOR:
+ case Opcodes.LOR:
+ case Opcodes.IXOR:
+ case Opcodes.LXOR:
+ instructions.add(new CfBinop(opcode));
+ break;
+ case Opcodes.I2L:
+ case Opcodes.I2F:
+ case Opcodes.I2D:
+ case Opcodes.L2I:
+ case Opcodes.L2F:
+ case Opcodes.L2D:
+ case Opcodes.F2I:
+ case Opcodes.F2L:
+ case Opcodes.F2D:
+ case Opcodes.D2I:
+ case Opcodes.D2L:
+ case Opcodes.D2F:
+ case Opcodes.I2B:
+ case Opcodes.I2C:
+ case Opcodes.I2S:
+ instructions.add(new CfUnop(opcode));
+ break;
+ case Opcodes.LCMP:
+ case Opcodes.FCMPL:
+ case Opcodes.FCMPG:
+ case Opcodes.DCMPL:
+ case Opcodes.DCMPG:
+ instructions.add(new CfBinop(opcode));
+ break;
+ case Opcodes.IRETURN:
+ instructions.add(new CfReturn(ValueType.INT));
+ break;
+ case Opcodes.LRETURN:
+ instructions.add(new CfReturn(ValueType.LONG));
+ break;
+ case Opcodes.FRETURN:
+ instructions.add(new CfReturn(ValueType.FLOAT));
+ break;
+ case Opcodes.DRETURN:
+ instructions.add(new CfReturn(ValueType.DOUBLE));
+ break;
+ case Opcodes.ARETURN:
+ instructions.add(new CfReturn(ValueType.OBJECT));
+ break;
+ case Opcodes.RETURN:
+ instructions.add(new CfReturnVoid());
+ break;
+ case Opcodes.ARRAYLENGTH:
+ instructions.add(new CfArrayLength());
+ break;
+ case Opcodes.ATHROW:
+ instructions.add(new CfThrow());
+ break;
+ case Opcodes.MONITORENTER:
+ instructions.add(new CfMonitor(Monitor.Type.ENTER));
+ break;
+ case Opcodes.MONITOREXIT:
+ instructions.add(new CfMonitor(Monitor.Type.EXIT));
+ break;
+ default:
+ throw new Unreachable("Unknown instruction");
+ }
+ }
+
+ private DexType opType(int opcode, DexItemFactory factory) {
+ switch (opcode) {
+ case Opcodes.IADD:
+ case Opcodes.ISUB:
+ case Opcodes.IMUL:
+ case Opcodes.IDIV:
+ case Opcodes.IREM:
+ case Opcodes.INEG:
+ case Opcodes.ISHL:
+ case Opcodes.ISHR:
+ case Opcodes.IUSHR:
+ return factory.intType;
+ case Opcodes.LADD:
+ case Opcodes.LSUB:
+ case Opcodes.LMUL:
+ case Opcodes.LDIV:
+ case Opcodes.LREM:
+ case Opcodes.LNEG:
+ case Opcodes.LSHL:
+ case Opcodes.LSHR:
+ case Opcodes.LUSHR:
+ return factory.longType;
+ case Opcodes.FADD:
+ case Opcodes.FSUB:
+ case Opcodes.FMUL:
+ case Opcodes.FDIV:
+ case Opcodes.FREM:
+ case Opcodes.FNEG:
+ return factory.floatType;
+ case Opcodes.DADD:
+ case Opcodes.DSUB:
+ case Opcodes.DMUL:
+ case Opcodes.DDIV:
+ case Opcodes.DREM:
+ case Opcodes.DNEG:
+ return factory.doubleType;
+ default:
+ throw new Unreachable("Unexpected opcode " + opcode);
+ }
+ }
+
+ private static MemberType getMemberTypeForOpcode(int opcode) {
+ switch (opcode) {
+ case Opcodes.IALOAD:
+ case Opcodes.IASTORE:
+ return MemberType.INT;
+ case Opcodes.FALOAD:
+ case Opcodes.FASTORE:
+ return MemberType.FLOAT;
+ case Opcodes.LALOAD:
+ case Opcodes.LASTORE:
+ return MemberType.LONG;
+ case Opcodes.DALOAD:
+ case Opcodes.DASTORE:
+ return MemberType.DOUBLE;
+ case Opcodes.AALOAD:
+ case Opcodes.AASTORE:
+ return MemberType.OBJECT;
+ case Opcodes.BALOAD:
+ case Opcodes.BASTORE:
+ return MemberType.BOOLEAN; // TODO: Distinguish byte and boolean.
+ case Opcodes.CALOAD:
+ case Opcodes.CASTORE:
+ return MemberType.CHAR;
+ case Opcodes.SALOAD:
+ case Opcodes.SASTORE:
+ return MemberType.SHORT;
+ default:
+ throw new Unreachable("Unexpected array opcode " + opcode);
+ }
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ switch (opcode) {
+ case Opcodes.SIPUSH:
+ case Opcodes.BIPUSH:
+ instructions.add(new CfConstNumber(operand, ValueType.INT));
+ break;
+ case Opcodes.NEWARRAY:
+ instructions.add(
+ new CfNewArray(factory.createArrayType(1, arrayTypeDesc(operand, factory))));
+ break;
+ default:
+ throw new Unreachable("Unexpected int opcode " + opcode);
+ }
+ }
+
+ private static DexType arrayTypeDesc(int arrayTypeCode, DexItemFactory factory) {
+ switch (arrayTypeCode) {
+ case Opcodes.T_BOOLEAN:
+ return factory.booleanType;
+ case Opcodes.T_CHAR:
+ return factory.charType;
+ case Opcodes.T_FLOAT:
+ return factory.floatType;
+ case Opcodes.T_DOUBLE:
+ return factory.doubleType;
+ case Opcodes.T_BYTE:
+ return factory.byteType;
+ case Opcodes.T_SHORT:
+ return factory.shortType;
+ case Opcodes.T_INT:
+ return factory.intType;
+ case Opcodes.T_LONG:
+ return factory.longType;
+ default:
+ throw new Unreachable("Unexpected array-type code " + arrayTypeCode);
+ }
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ ValueType type;
+ switch (opcode) {
+ case Opcodes.ILOAD:
+ case Opcodes.ISTORE:
+ type = ValueType.INT;
+ break;
+ case Opcodes.FLOAD:
+ case Opcodes.FSTORE:
+ type = ValueType.FLOAT;
+ break;
+ case Opcodes.LLOAD:
+ case Opcodes.LSTORE:
+ type = ValueType.LONG;
+ break;
+ case Opcodes.DLOAD:
+ case Opcodes.DSTORE:
+ type = ValueType.DOUBLE;
+ break;
+ case Opcodes.ALOAD:
+ case Opcodes.ASTORE:
+ type = ValueType.OBJECT;
+ break;
+ case Opcodes.RET:
+ throw new Unreachable("RET should be handled by the ASM jsr inliner");
+ default:
+ throw new Unreachable("Unexpected VarInsn opcode: " + opcode);
+ }
+ if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) {
+ instructions.add(new CfLoad(type, var));
+ } else {
+ instructions.add(new CfStore(type, var));
+ }
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String typeName) {
+ DexType type = createTypeFromInternalType(typeName);
+ switch (opcode) {
+ case Opcodes.NEW:
+ instructions.add(new CfNew(type));
+ break;
+ case Opcodes.ANEWARRAY:
+ instructions.add(new CfNewArray(factory.createArrayType(1, type)));
+ break;
+ case Opcodes.CHECKCAST:
+ instructions.add(new CfCheckCast(type));
+ break;
+ case Opcodes.INSTANCEOF:
+ instructions.add(new CfInstanceOf(type));
+ break;
+ default:
+ throw new Unreachable("Unexpected TypeInsn opcode: " + opcode);
+ }
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ DexField field =
+ factory.createField(createTypeFromInternalType(owner), factory.createType(desc), name);
+ // TODO(mathiasr): Don't require CfFieldInstruction::declaringField. It is needed for proper
+ // renaming in the backend, but it is not available here in the frontend.
+ instructions.add(new CfFieldInstruction(opcode, field, field));
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ visitMethodInsn(opcode, owner, name, desc, false);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ DexMethod method = application.getMethod(owner, name, desc);
+ instructions.add(new CfInvoke(opcode, method, itf));
+ }
+
+ @Override
+ public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
+ DexCallSite callSite =
+ DexCallSite.fromAsmInvokeDynamic(application, parent.type, name, desc, bsm, bsmArgs);
+ instructions.add(new CfInvokeDynamic(callSite));
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ CfLabel target = getLabel(label);
+ if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) {
+ if (opcode <= Opcodes.IFLE) {
+ // IFEQ, IFNE, IFLT, IFGE, IFGT, or IFLE.
+ instructions.add(new CfIf(ifType(opcode), ValueType.INT, target));
+ } else {
+ // IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, or
+ // IF_ACMPNE.
+ ValueType valueType;
+ if (opcode <= Opcodes.IF_ICMPLE) {
+ valueType = ValueType.INT;
+ } else {
+ valueType = ValueType.OBJECT;
+ }
+ instructions.add(new CfIfCmp(ifType(opcode), valueType, target));
+ }
+ } else {
+ // GOTO, JSR, IFNULL or IFNONNULL.
+ switch (opcode) {
+ case Opcodes.GOTO:
+ instructions.add(new CfGoto(target));
+ break;
+ case Opcodes.IFNULL:
+ case Opcodes.IFNONNULL:
+ If.Type type = opcode == Opcodes.IFNULL ? If.Type.EQ : If.Type.NE;
+ instructions.add(new CfIf(type, ValueType.OBJECT, target));
+ break;
+ case Opcodes.JSR:
+ throw new Unreachable("JSR should be handled by the ASM jsr inliner");
+ default:
+ throw new Unreachable("Unexpected JumpInsn opcode: " + opcode);
+ }
+ }
+ }
+
+ private static If.Type ifType(int opcode) {
+ switch (opcode) {
+ case Opcodes.IFEQ:
+ case Opcodes.IF_ICMPEQ:
+ case Opcodes.IF_ACMPEQ:
+ return If.Type.EQ;
+ case Opcodes.IFNE:
+ case Opcodes.IF_ICMPNE:
+ case Opcodes.IF_ACMPNE:
+ return If.Type.NE;
+ case Opcodes.IFLT:
+ case Opcodes.IF_ICMPLT:
+ return If.Type.LT;
+ case Opcodes.IFGE:
+ case Opcodes.IF_ICMPGE:
+ return If.Type.GE;
+ case Opcodes.IFGT:
+ case Opcodes.IF_ICMPGT:
+ return If.Type.GT;
+ case Opcodes.IFLE:
+ case Opcodes.IF_ICMPLE:
+ return If.Type.LE;
+ default:
+ throw new Unreachable("Unexpected If instruction opcode: " + opcode);
+ }
+ }
+
+ @Override
+ public void visitLabel(Label label) {
+ instructions.add(getLabel(label));
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type) {
+ Type type = (Type) cst;
+ if (type.getSort() == Type.METHOD) {
+ DexProto proto = application.getProto(type.getDescriptor());
+ instructions.add(new CfConstMethodType(proto));
+ } else {
+ instructions.add(new CfConstClass(factory.createType(type.getDescriptor())));
+ }
+ } else if (cst instanceof String) {
+ instructions.add(new CfConstString(factory.createString((String) cst)));
+ } else if (cst instanceof Long) {
+ instructions.add(new CfConstNumber((Long) cst, ValueType.LONG));
+ } else if (cst instanceof Double) {
+ long l = Double.doubleToRawLongBits((Double) cst);
+ instructions.add(new CfConstNumber(l, ValueType.DOUBLE));
+ } else if (cst instanceof Integer) {
+ instructions.add(new CfConstNumber((Integer) cst, ValueType.INT));
+ } else if (cst instanceof Float) {
+ long i = Float.floatToRawIntBits((Float) cst);
+ instructions.add(new CfConstNumber(i, ValueType.FLOAT));
+ } else if (cst instanceof Handle) {
+ instructions.add(
+ new CfConstMethodHandle(
+ DexMethodHandle.fromAsmHandle((Handle) cst, application, parent.type)));
+ } else {
+ throw new CompilationError("Unsupported constant: " + cst.toString());
+ }
+ }
+
+ @Override
+ public void visitIincInsn(int var, int increment) {
+ instructions.add(new CfIinc(var, increment));
+ }
+
+ @Override
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
+ // TODO(mathiasr): Support emitting table switches in CfSwitch.
+ throw new Unimplemented("Table switches not supported in CF backend");
+ }
+
+ @Override
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
+ for (Label label : labels) {
+ targets.add(getLabel(label));
+ }
+ instructions.add(new CfSwitch(getLabel(dflt), keys, targets));
+ }
+
+ @Override
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ instructions.add(new CfMultiANewArray(factory.createType(desc), dims));
+ }
+
+ @Override
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ List<DexType> guards =
+ Collections.singletonList(
+ type == null ? DexItemFactory.catchAllType : createTypeFromInternalType(type));
+ List<CfLabel> targets = Collections.singletonList(getLabel(handler));
+ tryCatchRanges.add(new CfTryCatch(getLabel(start), getLabel(end), guards, targets));
+ }
+
+ @Override
+ public void visitLocalVariable(
+ String name, String desc, String signature, Label start, Label end, int index) {
+ DebugLocalInfo debugLocalInfo =
+ new DebugLocalInfo(
+ factory.createString(name),
+ factory.createType(desc),
+ signature == null ? null : factory.createString(signature));
+ localVariables.add(
+ new LocalVariableInfo(index, debugLocalInfo, getLabel(start), getLabel(end)));
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ instructions.add(new CfPosition(getLabel(start), new Position(line, null, method, null)));
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ assert maxStack >= 0;
+ assert maxLocals >= 0;
+ this.maxStack = maxStack;
+ this.maxLocals = maxLocals;
+ }
+ }
+
private static class CreateAnnotationVisitor extends AnnotationVisitor {
private final JarApplicationReader application;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index d945dea..6ba1d71 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -263,7 +263,8 @@
if (!tryCatchHandlers.isEmpty()) {
// Close try-catch and save the range.
CfLabel tryCatchEnd = getLabel(block);
- tryCatchRanges.add(new CfTryCatch(tryCatchStart, tryCatchEnd, tryCatchHandlers, this));
+ tryCatchRanges.add(
+ CfTryCatch.fromBuilder(tryCatchStart, tryCatchEnd, tryCatchHandlers, this));
emitLabel(tryCatchEnd);
}
if (!handlers.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 2b6ad47..f44bc25 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -568,6 +568,10 @@
Log.debug(getClass(), "Original code for %s:\n%s",
method.toSourceString(), logCode(options, method));
}
+ if (options.skipIR) {
+ feedback.markProcessed(method, Constraint.NEVER);
+ return;
+ }
IRCode code = method.buildIR(options);
if (code == null) {
feedback.markProcessed(method, Constraint.NEVER);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 9fd675e..c5ba5cc 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -30,6 +30,10 @@
public class InternalOptions {
+ // Set to true to run compilation in a single thread and without randomly shuffling the input.
+ // This makes life easier when running R8 in a debugger.
+ public static final boolean DETERMINISTIC_DEBUGGING = false;
+
public enum LineNumberOptimization {
OFF,
ON,
@@ -94,7 +98,7 @@
public boolean enableValuePropagation = true;
// Number of threads to use while processing the dex files.
- public int numberOfThreads = ThreadUtils.NOT_SPECIFIED;
+ public int numberOfThreads = DETERMINISTIC_DEBUGGING ? 1 : ThreadUtils.NOT_SPECIFIED;
// Print smali disassembly.
public boolean useSmaliSyntax = false;
// Verbose output.
@@ -189,6 +193,10 @@
public boolean enableMinification = true;
public boolean disableAssertions = true;
public boolean debugKeepRules = false;
+ // Read input classes into CfCode format (instead of JarCode).
+ public boolean enableCfFrontend = false;
+ // Don't convert Code objects to IRCode.
+ public boolean skipIR = false;
public boolean debug = false;
public final TestingOptions testing = new TestingOptions();
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
new file mode 100644
index 0000000..ece865e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -0,0 +1,244 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.google.common.io.ByteStreams.toByteArray;
+import static org.junit.Assert.assertEquals;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.util.ASMifier;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+@RunWith(Parameterized.class)
+public class CfFrontendExamplesTest extends TestBase {
+
+ static final Collection<Object[]> TESTS = Arrays.asList(
+ makeTest("arithmetic.Arithmetic"),
+ makeTest("arrayaccess.ArrayAccess"),
+ makeTest("barray.BArray"),
+ makeTest("bridge.BridgeMethod"),
+ makeTest("cse.CommonSubexpressionElimination"),
+ makeTest("constants.Constants"),
+ makeTest("controlflow.ControlFlow"),
+ makeTest("conversions.Conversions"),
+ makeTest("floating_point_annotations.FloatingPointValuedAnnotationTest"),
+ makeTest("filledarray.FilledArray"),
+ makeTest("hello.Hello"),
+ makeTest("ifstatements.IfStatements"),
+ makeTest("instancevariable.InstanceVariable"),
+ makeTest("instanceofstring.InstanceofString"),
+ makeTest("invoke.Invoke"),
+ makeTest("jumbostring.JumboString"),
+ makeTest("loadconst.LoadConst"),
+ makeTest("loop.UdpServer"),
+ makeTest("newarray.NewArray"),
+ makeTest("regalloc.RegAlloc"),
+ makeTest("returns.Returns"),
+ makeTest("staticfield.StaticField"),
+ makeTest("stringbuilding.StringBuilding"),
+ makeTest("throwing.Throwing"),
+ makeTest("trivial.Trivial"),
+ makeTest("trycatch.TryCatch"),
+ makeTest("nestedtrycatches.NestedTryCatches"),
+ makeTest("trycatchmany.TryCatchMany"),
+ makeTest("invokeempty.InvokeEmpty"),
+ makeTest("regress.Regress"),
+ makeTest("regress2.Regress2"),
+ makeTest("regress_37726195.Regress"),
+ makeTest("regress_37658666.Regress", CfFrontendExamplesTest::compareRegress37658666),
+ makeTest("regress_37875803.Regress"),
+ makeTest("regress_37955340.Regress"),
+ // TODO(mathiasr): This test fails because we remove ASM's java.lang.Synthetic annotations.
+ // makeTest("regress_62300145.Regress"),
+ makeTest("regress_64881691.Regress"),
+ makeTest("regress_65104300.Regress"),
+ makeTest("regress_70703087.Test"),
+ makeTest("regress_70736958.Test"),
+ // TODO(mathiasr): Support uninitialized values in stack slots in CfFrame.
+ // makeTest("regress_70737019.Test"),
+ makeTest("regress_72361252.Test"),
+ makeTest("memberrebinding3.Memberrebinding"),
+ makeTest("minification.Minification"),
+ makeTest("enclosingmethod.Main"),
+ makeTest("enclosingmethod_proguarded.Main"),
+ makeTest("interfaceinlining.Main")
+ // TODO(mathiasr): These fail because we add a zero initializer to an field with no initializer.
+ // makeTest("sync.Sync"),
+ // makeTest("memberrebinding2.Memberrebinding"),
+ // TODO(mathiasr): Support emitting table switches in CfSwitch.
+ // makeTest("switches.Switches"),
+ // makeTest("switchmaps.Switches"),
+ );
+
+ private static Object[] makeTest(String className) {
+ return makeTest(className, null);
+ }
+
+ private static Object[] makeTest(String className, BiConsumer<byte[], byte[]> comparator) {
+ return new Object[] {className, comparator};
+ }
+
+ @Parameters(name = "{0}")
+ public static Collection<Object[]> data() {
+ return TESTS;
+ }
+
+ private static void compareRegress37658666(byte[] expectedBytes, byte[] actualBytes) {
+ // javac emits LDC(-0.0f) instead of the shorter FCONST_0 FNEG emitted by CfConstNumber.
+ String ldc = "mv.visitLdcInsn(new Float(\"-0.0\"));";
+ String constNeg = "mv.visitInsn(FCONST_0);\nmv.visitInsn(FNEG);";
+ assertEquals(
+ asmToString(expectedBytes).replace(ldc, constNeg),
+ asmToString(actualBytes));
+ }
+
+ private final Path inputJar;
+ private final BiConsumer<byte[], byte[]> comparator;
+
+ public CfFrontendExamplesTest(String clazz, BiConsumer<byte[], byte[]> comparator) {
+ this.comparator = comparator;
+ String pkg = clazz.substring(0, clazz.lastIndexOf('.'));
+ String suffix = "_debuginfo_all";
+ inputJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, pkg + suffix + JAR_EXTENSION);
+ }
+
+ @Test
+ public void test() throws Exception {
+ Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+ R8Command command =
+ R8Command.builder()
+ .addProgramFiles(inputJar)
+ .setMode(CompilationMode.DEBUG)
+ .setOutput(outputJar, OutputMode.ClassFile)
+ .build();
+ ToolHelper.runR8(
+ command,
+ options -> {
+ options.skipIR = true;
+ options.enableCfFrontend = true;
+ });
+ ArchiveClassFileProvider expected = new ArchiveClassFileProvider(inputJar);
+ ArchiveClassFileProvider actual = new ArchiveClassFileProvider(outputJar);
+ assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
+ for (String descriptor : expected.getClassDescriptors()) {
+ byte[] expectedBytes = getClassAsBytes(expected, descriptor);
+ byte[] actualBytes = getClassAsBytes(actual, descriptor);
+ if (comparator != null) {
+ comparator.accept(expectedBytes, actualBytes);
+ } else if (!Arrays.equals(expectedBytes, actualBytes)) {
+ assertEquals(
+ "Class " + descriptor + " differs",
+ asmToString(expectedBytes),
+ asmToString(actualBytes));
+ }
+ }
+ }
+
+ private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+ ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+ Collections.sort(descriptorList);
+ return descriptorList;
+ }
+
+ private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+ throws Exception {
+ return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+ }
+
+ private static String asmToString(byte[] clazz) {
+ StringWriter stringWriter = new StringWriter();
+ printAsm(new PrintWriter(stringWriter), clazz);
+ return stringWriter.toString();
+ }
+
+ private static void printAsm(PrintWriter pw, byte[] clazz) {
+ new ClassReader(clazz).accept(new TraceClassVisitor(null, new ASMifierSorted(), pw), 0);
+ }
+
+ /** Sort methods and fields in the output of ASMifier to make diffing possible. */
+ private static class ASMifierSorted extends ASMifier {
+ private static class Part implements Comparable<Part> {
+
+ private final String key;
+ private final int start;
+ private final int end;
+
+ Part(String key, int start, int end) {
+ this.key = key;
+ this.start = start;
+ this.end = end;
+ }
+
+ @Override
+ public int compareTo(Part part) {
+ int i = key.compareTo(part.key);
+ return i != 0 ? i : Integer.compare(start, part.start);
+ }
+ }
+
+ private final List<Part> parts = new ArrayList<>();
+
+ ASMifierSorted() {
+ super(Opcodes.ASM6, "cw", 0);
+ }
+
+ @Override
+ public ASMifier visitField(
+ int access, String name, String desc, String signature, Object value) {
+ init();
+ int i = text.size();
+ ASMifier res = super.visitField(access, name, desc, signature, value);
+ parts.add(new Part((String) text.get(i), i, text.size()));
+ return res;
+ }
+
+ @Override
+ public ASMifier visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ init();
+ int i = text.size();
+ ASMifier res = super.visitMethod(access, name, desc, signature, exceptions);
+ parts.add(new Part((String) text.get(i), i, text.size()));
+ return res;
+ }
+
+ private void init() {
+ if (parts.isEmpty()) {
+ parts.add(new Part("", 0, text.size()));
+ }
+ }
+
+ @Override
+ public void print(PrintWriter pw) {
+ init();
+ int end = parts.get(parts.size() - 1).end;
+ Collections.sort(parts);
+ parts.add(new Part("", end, text.size()));
+ ArrayList<Object> tmp = new ArrayList<>(text);
+ text.clear();
+ for (Part part : parts) {
+ for (int i = part.start; i < part.end; i++) {
+ text.add(tmp.get(i));
+ }
+ }
+ super.print(pw);
+ }
+ }
+}