Merge "Collect wildcards while parsing Proguard configuration contents."
diff --git a/.gitignore b/.gitignore
index 41e22c4..3bb46e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
build/
buildSrc/out/
.idea/
-r8.iml
.gradle/
android-data*/
tests/2016-12-19/art.tar.gz
@@ -68,6 +67,8 @@
third_party/core-lambda-stubs.tar.gz
third_party/openjdk/openjdk-rt-1.8
third_party/openjdk/openjdk-rt-1.8.tar.gz
+third_party/r8
+third_party/r8.tar.gz
src/test/jack/ub-jack
gradle-app.setting
gradlew
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6821c4e..df3d81b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -29,7 +29,6 @@
import com.android.tools.r8.naming.ProguardMapSupplier;
import com.android.tools.r8.naming.SeedMapper;
import com.android.tools.r8.naming.SourceFileRewriter;
-import com.android.tools.r8.optimize.BridgeMethodAnalysis;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.optimize.VisibilityBridgeRemover;
import com.android.tools.r8.origin.CommandLineOrigin;
@@ -359,11 +358,15 @@
}
application = application.asDirect().rewrittenWithLense(graphLense);
appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense);
- // Collect switch maps and ordinals maps.
- appInfo = new SwitchMapCollector(appInfo.withLiveness(), options).run();
- appInfo = new EnumOrdinalMapCollector(appInfo.withLiveness(), options).run();
+ // TODO(mathiasr): Remove this check when CF->IR construction is complete.
+ if (!options.skipIR) {
+ // Collect switch maps and ordinals maps.
+ appInfo = new SwitchMapCollector(appInfo.withLiveness(), options).run();
+ appInfo = new EnumOrdinalMapCollector(appInfo.withLiveness(), options).run();
+ }
- graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
+ // TODO(b/79143143): re-enable once fixed.
+ // graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
}
timing.begin("Create IR");
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index d2969d6..7fee6fe 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -153,8 +153,9 @@
}
for (DexEncodedField field : classDef.staticFields()) {
- if (field.staticValue != null) {
- processFieldValue(field.staticValue);
+ DexValue staticValue = field.getStaticValue();
+ if (staticValue != null) {
+ processFieldValue(staticValue);
}
}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 2fe8952..b116e65 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "v1.2.11-dev";
+ public static final String LABEL = "v1.2.14-dev";
private Version() {
}
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..300335a 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -16,12 +16,11 @@
import com.android.tools.r8.cf.code.CfConstString;
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.CfFrame.FrameType;
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;
@@ -33,10 +32,10 @@
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.CfStackInstruction;
import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.cf.code.CfSwitch;
import com.android.tools.r8.cf.code.CfSwitch.Kind;
@@ -57,8 +56,6 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.StringUtils.BraceType;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.IntList;
import java.util.HashMap;
@@ -152,6 +149,40 @@
print("nop");
}
+ public void print(CfStackInstruction instruction) {
+ switch (instruction.getOpcode()) {
+ case Pop:
+ print("pop");
+ return;
+ case Pop2:
+ print("pop2");
+ return;
+ case Dup:
+ print("dup");
+ return;
+ case DupX1:
+ print("dup_x1");
+ return;
+ case DupX2:
+ print("dup_x2");
+ return;
+ case Dup2:
+ print("dup2");
+ return;
+ case Dup2X1:
+ print("dup2_x1");
+ return;
+ case Dup2X2:
+ print("dup2_x2");
+ return;
+ case Swap:
+ print("swap");
+ return;
+ default:
+ throw new Unreachable("Invalid instruction for CfStackInstruction");
+ }
+ }
+
public void print(CfThrow insn) {
print("athrow");
}
@@ -207,10 +238,6 @@
print(opcodeName(unop.getOpcode()));
}
- public void print(CfPop pop) {
- print("pop");
- }
-
public void print(CfConstString constString) {
indent();
builder.append("ldc ").append(constString.getString());
@@ -241,26 +268,35 @@
public void print(CfFrame frame) {
StringBuilder builder = new StringBuilder("frame: [");
- String separator = "";
- for (Entry<DexType> entry : frame.getLocals().int2ReferenceEntrySet()) {
- builder.append(separator).append(entry.getIntKey()).append(':');
- Uninitialized allocator = frame.getAllocators().get(entry.getIntKey());
- if (allocator == null) {
- builder.append(entry.getValue());
- } else if (allocator instanceof UninitializedThis) {
- builder.append("uninitialized this");
- } else {
- builder
- .append("uninitialized ")
- .append(getLabel(((UninitializedNew) allocator).getLabel()));
+ {
+ String separator = "";
+ for (Entry<FrameType> entry : frame.getLocals().int2ReferenceEntrySet()) {
+ builder.append(separator).append(entry.getIntKey()).append(':');
+ print(entry.getValue(), builder);
+ separator = ", ";
}
- separator = ", ";
}
- builder.append("] ");
- StringUtils.append(builder, frame.getStack(), ", ", BraceType.SQUARE);
+ builder.append("] [");
+ {
+ String separator = "";
+ for (FrameType element : frame.getStack()) {
+ builder.append(separator);
+ print(element, builder);
+ separator = ", ";
+ }
+ }
+ builder.append(']');
comment(builder.toString());
}
+ private void print(FrameType type, StringBuilder builder) {
+ if (type.isUninitializedNew()) {
+ builder.append("uninitialized ").append(getLabel(type.getUninitializedLabel()));
+ } else {
+ builder.append(type.toString());
+ }
+ }
+
public void print(CfInstanceOf insn) {
indent();
builder.append("instanceof ");
@@ -376,14 +412,16 @@
public void print(CfSwitch cfSwitch) {
indent();
- builder.append(cfSwitch.getKind() == Kind.LOOKUP ? "lookup" : "table").append("switch");
+ Kind kind = cfSwitch.getKind();
+ builder.append(kind == Kind.LOOKUP ? "lookup" : "table").append("switch");
IntList keys = cfSwitch.getKeys();
List<CfLabel> targets = cfSwitch.getTargets();
- for (int i = 0; i < keys.size(); i++) {
+ for (int i = 0; i < targets.size(); i++) {
indent();
+ int key = kind == Kind.LOOKUP ? keys.getInt(i) : (keys.getInt(0) + i);
builder
.append(" ")
- .append(keys.getInt(i))
+ .append(key)
.append(": ")
.append(getLabel(targets.get(i)));
}
@@ -401,6 +439,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/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index ae43001..b4a6009 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -5,7 +5,6 @@
import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -87,11 +86,6 @@
}
@Override
- public boolean argumentValueUsesHighRegister(Value value, int instructionNumber) {
- throw new Unreachable();
- }
-
- @Override
public int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber) {
return getRegisterForValue(value);
}
@@ -144,6 +138,7 @@
while (!unhandled.isEmpty()) {
LiveIntervals unhandledInterval = unhandled.poll();
+ assert !unhandledInterval.getValue().isArgument();
int start = unhandledInterval.getStart();
{
// Check for active intervals that expired or became inactive.
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 07fd664..f3aa7f3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -56,6 +56,10 @@
int value = getIntValue();
if (-1 <= value && value <= 5) {
visitor.visitInsn(Opcodes.ICONST_0 + value);
+ } else if (Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE) {
+ visitor.visitIntInsn(Opcodes.BIPUSH, value);
+ } else if (Short.MIN_VALUE <= value && value <= Short.MAX_VALUE) {
+ visitor.visitIntInsn(Opcodes.SIPUSH, value);
} else {
visitor.visitLdcInsn(value);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index b5e759c..d015de1 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -12,61 +12,155 @@
import com.android.tools.r8.naming.NamingLens;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import java.util.List;
+import java.util.Objects;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class CfFrame extends CfInstruction {
- public abstract static class Uninitialized {
- abstract Object getAsmLabel();
+ public abstract static class FrameType {
+
+ public static FrameType initialized(DexType type) {
+ return new InitializedType(type);
+ }
+
+ public static FrameType uninitializedNew(CfLabel label) {
+ return new UninitializedNew(label);
+ }
+
+ public static FrameType uninitializedThis() {
+ return new UninitializedThis();
+ }
+
+ public static FrameType top() {
+ return Top.SINGLETON;
+ }
+
+ abstract Object getTypeOpcode(NamingLens lens);
+
+ public boolean isWide() {
+ return false;
+ }
+
+ public boolean isUninitializedNew() {
+ return false;
+ }
+
+ public CfLabel getUninitializedLabel() {
+ return null;
+ }
+
+ private FrameType() {}
}
- public static class UninitializedNew extends Uninitialized {
+ private static class InitializedType extends FrameType {
+
+ private final DexType type;
+
+ private InitializedType(DexType type) {
+ assert type != null;
+ this.type = type;
+ }
+
+ @Override
+ public String toString() {
+ return type.toString();
+ }
+
+ @Override
+ Object getTypeOpcode(NamingLens lens) {
+ if (type == DexItemFactory.nullValueType) {
+ return Opcodes.NULL;
+ }
+ switch (type.toShorty()) {
+ case 'L':
+ return lens.lookupInternalName(type);
+ case 'I':
+ return Opcodes.INTEGER;
+ case 'F':
+ return Opcodes.FLOAT;
+ case 'J':
+ return Opcodes.LONG;
+ case 'D':
+ return Opcodes.DOUBLE;
+ default:
+ throw new Unreachable("Unexpected value type: " + type);
+ }
+ }
+
+ @Override
+ public boolean isWide() {
+ return type.isPrimitiveType() && (type.toShorty() == 'J' || type.toShorty() == 'D');
+ }
+ }
+
+ private static class Top extends FrameType {
+
+ private static final Top SINGLETON = new Top();
+
+ @Override
+ public String toString() {
+ return "top";
+ }
+
+ @Override
+ Object getTypeOpcode(NamingLens lens) {
+ return Opcodes.TOP;
+ }
+ }
+
+ private static class UninitializedNew extends FrameType {
private final CfLabel label;
- public UninitializedNew(CfLabel label) {
+ private UninitializedNew(CfLabel label) {
this.label = label;
}
@Override
- Object getAsmLabel() {
+ public String toString() {
+ return "uninitialized new";
+ }
+
+ @Override
+ Object getTypeOpcode(NamingLens lens) {
return label.getLabel();
}
- public CfLabel getLabel() {
+ @Override
+ public CfLabel getUninitializedLabel() {
return label;
}
}
- public static class UninitializedThis extends Uninitialized {
+ private static class UninitializedThis extends FrameType {
+ private UninitializedThis() {}
+
@Override
- Object getAsmLabel() {
+ Object getTypeOpcode(NamingLens lens) {
return Opcodes.UNINITIALIZED_THIS;
}
+
+ @Override
+ public String toString() {
+ return "uninitialized this";
+ }
}
- private final Int2ReferenceSortedMap<DexType> locals;
- private final Int2ReferenceSortedMap<Uninitialized> allocators;
- private final List<DexType> stack;
+ private final Int2ReferenceSortedMap<FrameType> locals;
+ private final List<FrameType> stack;
- public CfFrame(
- Int2ReferenceSortedMap<DexType> locals,
- Int2ReferenceSortedMap<Uninitialized> allocators,
- List<DexType> stack) {
+ public CfFrame(Int2ReferenceSortedMap<FrameType> locals, List<FrameType> stack) {
+ assert locals.values().stream().allMatch(Objects::nonNull);
+ assert stack.stream().allMatch(Objects::nonNull);
this.locals = locals;
- this.allocators = allocators;
this.stack = stack;
}
- public Int2ReferenceSortedMap<DexType> getLocals() {
+ public Int2ReferenceSortedMap<FrameType> getLocals() {
return locals;
}
- public Int2ReferenceSortedMap<Uninitialized> getAllocators() {
- return allocators;
- }
-
- public List<DexType> getStack() {
+ public List<FrameType> getStack() {
return stack;
}
@@ -79,10 +173,6 @@
visitor.visitFrame(F_NEW, localsCount, localsTypes, stackCount, stackTypes);
}
- private boolean isWide(DexType type) {
- return type.isPrimitiveType() && (type.toShorty() == 'J' || type.toShorty() == 'D');
- }
-
private int computeStackCount() {
return stack.size();
}
@@ -94,7 +184,7 @@
}
Object[] stackTypes = new Object[stackCount];
for (int i = 0; i < stackCount; i++) {
- stackTypes[i] = getType(stack.get(i), lens);
+ stackTypes[i] = stack.get(i).getTypeOpcode(lens);
}
return stackTypes;
}
@@ -108,8 +198,8 @@
int localsCount = 0;
for (int i = 0; i <= maxRegister; i++) {
localsCount++;
- DexType type = locals.get(i);
- if (type != null && isWide(type)) {
+ FrameType type = locals.get(i);
+ if (type != null && type.isWide()) {
i++;
}
}
@@ -124,40 +214,15 @@
Object[] localsTypes = new Object[localsCount];
int localIndex = 0;
for (int i = 0; i <= maxRegister; i++) {
- DexType type = locals.get(i);
- Uninitialized allocator = allocators.get(i);
- Object typeOpcode = allocator == null ? getType(type, lens) : allocator.getAsmLabel();
- localsTypes[localIndex++] = typeOpcode;
- if (type != null && isWide(type)) {
+ FrameType type = locals.get(i);
+ localsTypes[localIndex++] = type == null ? Opcodes.TOP : type.getTypeOpcode(lens);
+ if (type != null && type.isWide()) {
i++;
}
}
return localsTypes;
}
- private Object getType(DexType type, NamingLens lens) {
- if (type == null) {
- return Opcodes.TOP;
- }
- if (type == DexItemFactory.nullValueType) {
- return Opcodes.NULL;
- }
- switch (type.toShorty()) {
- case 'L':
- return lens.lookupInternalName(type);
- case 'I':
- return Opcodes.INTEGER;
- case 'F':
- return Opcodes.FLOAT;
- case 'J':
- return Opcodes.LONG;
- case 'D':
- return Opcodes.DOUBLE;
- default:
- throw new Unreachable("Unexpected value type: " + type);
- }
- }
-
@Override
public String toString() {
return getClass().getSimpleName();
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/CfPop.java b/src/main/java/com/android/tools/r8/cf/code/CfPop.java
deleted file mode 100644
index 9f485d2..0000000
--- a/src/main/java/com/android/tools/r8/cf/code/CfPop.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2017, 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.ir.code.ValueType;
-import com.android.tools.r8.naming.NamingLens;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-public class CfPop extends CfInstruction {
-
- private final ValueType type;
-
- public CfPop(ValueType type) {
- this.type = type;
- }
-
- @Override
- public void write(MethodVisitor visitor, NamingLens lens) {
- visitor.visitInsn(type.isWide() ? Opcodes.POP2 : Opcodes.POP);
- }
-
- @Override
- public void print(CfPrinter printer) {
- printer.print(this);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
new file mode 100644
index 0000000..efda3f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfStackInstruction.java
@@ -0,0 +1,81 @@
+// 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.errors.Unreachable;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.naming.NamingLens;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class CfStackInstruction extends CfInstruction {
+
+ public enum Opcode {
+ Pop(Opcodes.POP),
+ Pop2(Opcodes.POP2),
+ Dup(Opcodes.DUP),
+ DupX1(Opcodes.DUP_X1),
+ DupX2(Opcodes.DUP_X2),
+ Dup2(Opcodes.DUP2),
+ Dup2X1(Opcodes.DUP2_X1),
+ Dup2X2(Opcodes.DUP2_X2),
+ Swap(Opcodes.SWAP);
+
+ private final int opcode;
+
+ Opcode(int opcode) {
+ this.opcode = opcode;
+ }
+ }
+
+ private final Opcode opcode;
+
+ public static CfStackInstruction fromAsm(int opcode) {
+ switch (opcode) {
+ case Opcodes.POP:
+ return new CfStackInstruction(Opcode.Pop);
+ case Opcodes.POP2:
+ return new CfStackInstruction(Opcode.Pop2);
+ case Opcodes.DUP:
+ return new CfStackInstruction(Opcode.Dup);
+ case Opcodes.DUP_X1:
+ return new CfStackInstruction(Opcode.DupX1);
+ case Opcodes.DUP_X2:
+ return new CfStackInstruction(Opcode.DupX2);
+ case Opcodes.DUP2:
+ return new CfStackInstruction(Opcode.Dup2);
+ case Opcodes.DUP2_X1:
+ return new CfStackInstruction(Opcode.Dup2X1);
+ case Opcodes.DUP2_X2:
+ return new CfStackInstruction(Opcode.Dup2X2);
+ case Opcodes.SWAP:
+ return new CfStackInstruction(Opcode.Swap);
+ default:
+ throw new Unreachable("Invalid opcode for CfStackInstruction");
+ }
+ }
+
+ public static CfStackInstruction popType(ValueType type) {
+ return new CfStackInstruction(type.isWide() ? Opcode.Pop2 : Opcode.Pop);
+ }
+
+ public CfStackInstruction(Opcode opcode) {
+ this.opcode = opcode;
+ }
+
+ @Override
+ public void write(MethodVisitor visitor, NamingLens lens) {
+ visitor.visitInsn(opcode.opcode);
+ }
+
+ @Override
+ public void print(CfPrinter printer) {
+ printer.print(this);
+ }
+
+ public Opcode getOpcode() {
+ return opcode;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
index f27064c..1dc72d4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfSwitch.java
@@ -20,12 +20,13 @@
private final int[] keys;
private final List<CfLabel> targets;
- public CfSwitch(CfLabel defaultTarget, int[] keys, List<CfLabel> targets) {
- // TODO(zerny): Support emitting table switches.
- this.kind = Kind.LOOKUP;
+ public CfSwitch(Kind kind, CfLabel defaultTarget, int[] keys, List<CfLabel> targets) {
+ this.kind = kind;
this.defaultTarget = defaultTarget;
this.keys = keys;
this.targets = targets;
+ assert kind != Kind.LOOKUP || keys.length == targets.size();
+ assert kind != Kind.TABLE || keys.length == 1;
}
public Kind getKind() {
@@ -50,7 +51,16 @@
for (int i = 0; i < targets.size(); i++) {
labels[i] = targets.get(i).getLabel();
}
- visitor.visitLookupSwitchInsn(defaultTarget.getLabel(), keys, labels);
+ switch (kind) {
+ case LOOKUP:
+ visitor.visitLookupSwitchInsn(defaultTarget.getLabel(), keys, labels);
+ break;
+ case TABLE: {
+ int min = keys[0];
+ int max = min + labels.length - 1;
+ visitor.visitTableSwitchInsn(min, max, defaultTarget.getLabel(), labels);
+ }
+ }
}
@Override
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/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 958deb4..4524505 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -576,8 +576,6 @@
if (accessFlags.isStatic()) {
if (staticValues != null && i < staticValues.length) {
staticValue = staticValues[i];
- } else {
- staticValue = DexValue.defaultForType(field.type, dexItemFactory);
}
}
fields[i] = new DexEncodedField(field, accessFlags, fieldAnnotations, staticValue);
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/DexAnnotationSetRefList.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
index 336eac5..e6b4a2c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
@@ -12,6 +12,7 @@
private static final DexAnnotationSetRefList theEmptyTypeList = new DexAnnotationSetRefList();
public final DexAnnotationSet[] values;
+ private final int missingParameterAnnotations;
public static DexAnnotationSetRefList empty() {
return theEmptyTypeList;
@@ -19,11 +20,17 @@
private DexAnnotationSetRefList() {
this.values = new DexAnnotationSet[0];
+ this.missingParameterAnnotations = 0;
}
public DexAnnotationSetRefList(DexAnnotationSet[] values) {
+ this(values, 0);
+ }
+
+ public DexAnnotationSetRefList(DexAnnotationSet[] values, int missingParameterAnnotations) {
assert values != null && values.length > 0;
this.values = values;
+ this.missingParameterAnnotations = missingParameterAnnotations;
}
@Override
@@ -57,4 +64,8 @@
public boolean isEmpty() {
return values.length == 0;
}
+
+ public int getMissingParameterAnnotations() {
+ return missingParameterAnnotations;
+ }
}
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/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index e9b9255..7b09cab 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -369,7 +369,7 @@
public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
return Arrays.stream(staticFields())
- .anyMatch(field -> !field.staticValue.mayTriggerAllocation());
+ .anyMatch(field -> !field.getStaticValue().mayTriggerAllocation());
}
public List<InnerClassAttribute> getInnerClasses() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index bdfadfc..a28dc82 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -15,14 +15,13 @@
public final DexField field;
public final FieldAccessFlags accessFlags;
public DexAnnotationSet annotations;
- public DexValue staticValue;
+ private DexValue staticValue;
public DexEncodedField(
DexField field,
FieldAccessFlags accessFlags,
DexAnnotationSet annotations,
DexValue staticValue) {
- assert !accessFlags.isStatic() || staticValue != null;
this.field = field;
this.accessFlags = accessFlags;
this.annotations = annotations;
@@ -33,8 +32,8 @@
public void collectIndexedItems(IndexedItemCollection indexedItems) {
field.collectIndexedItems(indexedItems);
annotations.collectIndexedItems(indexedItems);
- if (staticValue != null) {
- staticValue.collectIndexedItems(indexedItems);
+ if (accessFlags.isStatic()) {
+ getStaticValue().collectIndexedItems(indexedItems);
}
}
@@ -67,6 +66,22 @@
return !annotations.isEmpty();
}
+ public boolean hasExplicitStaticValue() {
+ assert accessFlags.isStatic();
+ return staticValue != null;
+ }
+
+ public void setStaticValue(DexValue staticValue) {
+ assert accessFlags.isStatic();
+ assert staticValue != null;
+ this.staticValue = staticValue;
+ }
+
+ public DexValue getStaticValue() {
+ assert accessFlags.isStatic();
+ return staticValue == null ? DexValue.defaultForType(field.type) : staticValue;
+ }
+
// Returns a const instructions if this field is a compile time final const.
public Instruction valueAsConstInstruction(AppInfo appInfo, Value dest) {
// The only way to figure out whether the DexValue contains the final value
@@ -74,7 +89,7 @@
if (accessFlags.isStatic() && accessFlags.isPublic() && accessFlags.isFinal()) {
DexClass clazz = appInfo.definitionFor(field.getHolder());
assert clazz != null : "Class for the field must be present";
- return staticValue.asConstInstruction(clazz.hasClassInitializer(), dest);
+ return getStaticValue().asConstInstruction(clazz.hasClassInitializer(), dest);
}
return null;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 9644558..0fac5e8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -49,7 +49,7 @@
// Set all static field values to unknown. We don't want to use the value from the library
// at compile time, as it can be different at runtime.
for (DexEncodedField staticField : staticFields) {
- staticField.staticValue = DexValue.UNKNOWN;
+ staticField.setStaticValue(DexValue.UNKNOWN);
}
assert kind == Kind.CF : "Invalid kind " + kind + " for library-path class " + type;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 90db1d2..1473191 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -275,9 +275,10 @@
List<DexValue> values = new ArrayList<>(fields.length);
for (int i = 0; i < fields.length; i++) {
DexEncodedField field = fields[i];
- assert field.staticValue != null;
- values.add(field.staticValue);
- if (!field.staticValue.isDefault(field.field.type, factory)) {
+ DexValue staticValue = field.getStaticValue();
+ assert staticValue != null;
+ values.add(staticValue);
+ if (!staticValue.isDefault(field.field.type)) {
length = i + 1;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 570aa6f..3d0a177 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -92,7 +92,8 @@
}
public boolean isInterface() {
- assert isClassType() && hierarchyLevel != UNKNOWN_LEVEL;
+ assert hierarchyLevel != UNKNOWN_LEVEL : "Program class missing: " + this;
+ assert isClassType();
return hierarchyLevel == INTERFACE_LEVEL;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index f1a0f4d..342ea2a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -96,35 +96,29 @@
@Override
public abstract String toString();
- public static DexValue defaultForType(DexType type, DexItemFactory factory) {
- if (type == factory.booleanType) {
- return DexValueBoolean.DEFAULT;
+ public static DexValue defaultForType(DexType type) {
+ switch (type.toShorty()) {
+ case 'Z':
+ return DexValueBoolean.DEFAULT;
+ case 'B':
+ return DexValueByte.DEFAULT;
+ case 'C':
+ return DexValueChar.DEFAULT;
+ case 'S':
+ return DexValueShort.DEFAULT;
+ case 'I':
+ return DexValueInt.DEFAULT;
+ case 'J':
+ return DexValueLong.DEFAULT;
+ case 'F':
+ return DexValueFloat.DEFAULT;
+ case 'D':
+ return DexValueDouble.DEFAULT;
+ case 'L':
+ return DexValueNull.NULL;
+ default:
+ throw new Unreachable("No default value for unexpected type " + type);
}
- if (type == factory.byteType) {
- return DexValueByte.DEFAULT;
- }
- if (type == factory.charType) {
- return DexValueChar.DEFAULT;
- }
- if (type == factory.shortType) {
- return DexValueShort.DEFAULT;
- }
- if (type == factory.intType) {
- return DexValueInt.DEFAULT;
- }
- if (type == factory.longType) {
- return DexValueLong.DEFAULT;
- }
- if (type == factory.floatType) {
- return DexValueFloat.DEFAULT;
- }
- if (type == factory.doubleType) {
- return DexValueDouble.DEFAULT;
- }
- if (type.isArrayType() || type.isClassType()) {
- return DexValueNull.NULL;
- }
- throw new Unreachable("No default value for unexpected type " + type);
}
public abstract Object getBoxedValue();
@@ -134,8 +128,8 @@
return null;
}
- public boolean isDefault(DexType type, DexItemFactory factory) {
- return this == defaultForType(type, factory);
+ public boolean isDefault(DexType type) {
+ return this == defaultForType(type);
}
/**
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..bef2f57 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,54 @@
// 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.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.FrameType;
+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.CfPosition;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+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.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 +66,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 +90,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;
@@ -52,6 +104,8 @@
// Hidden ASM "synthetic attribute" bit we need to clear.
private static final int ACC_SYNTHETIC_ATTRIBUTE = 0x40000;
+ // Descriptor used by ASM for missing annotations.
+ public static final String SYNTHETIC_ANNOTATION = "Ljava/lang/Synthetic;";
private final JarApplicationReader application;
private final Consumer<DexClass> classConsumer;
@@ -64,8 +118,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 +268,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);
}
@@ -344,7 +405,7 @@
private DexValue getStaticValue(Object value, DexType type) {
if (value == null) {
- return DexValue.defaultForType(type, parent.application.getFactory());
+ return null;
}
DexItemFactory factory = parent.application.getFactory();
if (type == factory.booleanType) {
@@ -396,7 +457,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 +465,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 +476,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];
@@ -456,7 +522,7 @@
// with that descriptor. If javac is fixed, the ASM workaround will not be hit and we will
// never see this non-existing annotation descriptor. ASM uses the same check to make
// sure to undo their workaround for the javac bug in their MethodWriter.
- if (desc.equals("Ljava/lang/Synthetic;")) {
+ if (desc.equals(SYNTHETIC_ANNOTATION)) {
// We can iterate through all the parameters twice. Once for visible and once for
// invisible parameter annotations. We only record the number of fake parameter
// annotations once.
@@ -511,15 +577,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();
@@ -528,7 +596,7 @@
for (int i = 0; i < parameterAnnotations.size(); i++) {
sets[i] = createAnnotationSet(parameterAnnotations.get(i));
}
- parameterAnnotationSets = new DexAnnotationSetRefList(sets);
+ parameterAnnotationSets = new DexAnnotationSetRefList(sets, fakeParameterAnnotations);
}
InternalOptions internalOptions = parent.application.options;
if (parameterNames != null && internalOptions.canUseParameterNameAnnotations()) {
@@ -566,6 +634,646 @@
}
}
+ 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 frameType, int nLocals, Object[] localTypes, int nStack, Object[] stackTypes) {
+ assert frameType == Opcodes.F_NEW;
+ Int2ReferenceSortedMap<FrameType> parsedLocals = parseLocals(nLocals, localTypes);
+ List<FrameType> parsedStack = parseStack(nStack, stackTypes);
+ instructions.add(new CfFrame(parsedLocals, parsedStack));
+ }
+
+ private Int2ReferenceSortedMap<FrameType> parseLocals(int typeCount, Object[] asmTypes) {
+ Int2ReferenceSortedMap<FrameType> types = new Int2ReferenceAVLTreeMap<>();
+ int i = 0;
+ for (int j = 0; j < typeCount; j++) {
+ Object localType = asmTypes[j];
+ FrameType value = getFrameType(localType);
+ types.put(i++, value);
+ if (value.isWide()) {
+ i++;
+ }
+ }
+ return types;
+ }
+
+ private List<FrameType> parseStack(int nStack, Object[] stackTypes) {
+ List<FrameType> dexStack = new ArrayList<>(nStack);
+ for (int i = 0; i < nStack; i++) {
+ dexStack.add(getFrameType(stackTypes[i]));
+ }
+ return dexStack;
+ }
+
+ private FrameType getFrameType(Object localType) {
+ if (localType instanceof Label) {
+ return FrameType.uninitializedNew(getLabel((Label) localType));
+ } else if (localType == Opcodes.UNINITIALIZED_THIS) {
+ return FrameType.uninitializedThis();
+ } else if (localType == null || localType == Opcodes.TOP) {
+ return FrameType.top();
+ } else {
+ return FrameType.initialized(parseAsmType(localType));
+ }
+ }
+
+ private CfLabel getLabel(Label label) {
+ return labelMap.computeIfAbsent(label, l -> new CfLabel());
+ }
+
+ private DexType parseAsmType(Object local) {
+ assert local != null && local != Opcodes.TOP;
+ 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:
+ case Opcodes.POP2:
+ 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:
+ instructions.add(CfStackInstruction.fromAsm(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) {
+ assert max == min + labels.length - 1;
+ ArrayList<CfLabel> targets = new ArrayList<>(labels.length);
+ for (Label label : labels) {
+ targets.add(getLabel(label));
+ }
+ instructions.add(new CfSwitch(CfSwitch.Kind.TABLE, getLabel(dflt), new int[] {min}, targets));
+ }
+
+ @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(CfSwitch.Kind.LOOKUP, 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/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 24e6eb5..fb75931 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -42,6 +42,22 @@
private Int2ReferenceMap<DebugLocalInfo> localsAtEntry;
+ public boolean consistentBlockInstructions(boolean argumentsAllowed) {
+ for (Instruction instruction : getInstructions()) {
+ assert instruction.getPosition() != null;
+ assert instruction.getBlock() == this;
+ assert !instruction.isArgument() || argumentsAllowed;
+ assert !instruction.isDebugLocalRead() || !instruction.getDebugValues().isEmpty();
+ // TODO(b/79186787): Ensure DEX backend inserts Move *after* arguments.
+ if (!(instruction.isArgument()
+ || instruction.isMove()
+ || instruction.isDebugLocalsChange())) {
+ argumentsAllowed = false;
+ }
+ }
+ return true;
+ }
+
public void setLocalsAtEntry(Int2ReferenceMap<DebugLocalInfo> localsAtEntry) {
this.localsAtEntry = localsAtEntry;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index f179b9e..1a43128 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -49,16 +49,20 @@
// if the register allocator could not put input and output in the same register
// we have to insert a move before the check cast instruction.
int inRegister = builder.allocatedRegister(inValues.get(0), getNumber());
- int outRegister = builder.allocatedRegister(outValue, getNumber());
- if (inRegister == outRegister) {
- builder.add(this, new com.android.tools.r8.code.CheckCast(outRegister, type));
+ if (outValue == null) {
+ builder.add(this, new com.android.tools.r8.code.CheckCast(inRegister, type));
} else {
- com.android.tools.r8.code.CheckCast cast =
- new com.android.tools.r8.code.CheckCast(outRegister, type);
- if (outRegister <= Constants.U4BIT_MAX && inRegister <= Constants.U4BIT_MAX) {
- builder.add(this, new MoveObject(outRegister, inRegister), cast);
+ int outRegister = builder.allocatedRegister(outValue, getNumber());
+ if (inRegister == outRegister) {
+ builder.add(this, new com.android.tools.r8.code.CheckCast(outRegister, type));
} else {
- builder.add(this, new MoveObjectFrom16(outRegister, inRegister), cast);
+ com.android.tools.r8.code.CheckCast cast =
+ new com.android.tools.r8.code.CheckCast(outRegister, type);
+ if (outRegister <= Constants.U4BIT_MAX && inRegister <= Constants.U4BIT_MAX) {
+ builder.add(this, new MoveObject(outRegister, inRegister), cast);
+ } else {
+ builder.add(this, new MoveObjectFrom16(outRegister, inRegister), cast);
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index c728e6a..b763298 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -65,7 +65,7 @@
@Override
public String toString() {
if (getBlock() != null && !getBlock().getSuccessors().isEmpty()) {
- return super.toString() + "block " + getTarget().getNumber();
+ return super.toString() + "block " + blockNumberToString(getTarget());
}
return super.toString() + "block <unknown>";
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index beaa62b..bb13495 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -125,7 +125,8 @@
}
}
}
- assert liveAtEntrySets.get(sorted.get(0)).size() == 0;
+ assert liveAtEntrySets.get(sorted.get(0)).size() == 0
+ : "Unexpected values live at entry to first block: " + liveAtEntrySets.get(sorted.get(0));
return liveAtEntrySets;
}
@@ -494,11 +495,10 @@
}
private boolean consistentBlockInstructions() {
+ boolean argumentsAllowed = true;
for (BasicBlock block : blocks) {
- for (Instruction instruction : block.getInstructions()) {
- assert instruction.getPosition() != null;
- assert instruction.getBlock() == block;
- }
+ block.consistentBlockInstructions(argumentsAllowed);
+ argumentsAllowed = false;
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 001b648..0235a14 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -126,8 +126,14 @@
@Override
public String toString() {
- return super.toString() + " " + type + " block " + getTrueTarget().getNumber()
- + " (fallthrough " + fallthroughBlock().getNumber() + ")";
+ return super.toString()
+ + " "
+ + type
+ + " block "
+ + blockNumberToString(getTrueTarget())
+ + " (fallthrough "
+ + blockNumberToString(fallthroughBlock())
+ + ")";
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 1084bef..1c3423c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -26,6 +26,7 @@
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
@@ -164,11 +165,16 @@
public void replaceDebugValue(Value oldValue, Value newValue) {
if (debugValues.remove(oldValue)) {
+ // TODO(mathiasr): Enable this assertion when BasicBlock has current position so trivial phi
+ // removal can take local info into account.
+ // assert newValue.getLocalInfo() == oldValue.getLocalInfo()
+ // : "Replacing debug values with inconsistent locals " +
+ // oldValue.getLocalInfo() + " and " + newValue.getLocalInfo() +
+ // ". This is likely a code transformation bug " +
+ // "that has not taken local information into account";
if (newValue.hasLocalInfo()) {
- // TODO(zerny): Insert a write if replacing a phi with different debug-local info.
addDebugValue(newValue);
}
- // TODO(zerny): Else: Insert a write if replacing a phi with associated debug-local info.
}
}
@@ -200,6 +206,21 @@
assert false;
}
+ public Value removeDebugValue(DebugLocalInfo localInfo) {
+ if (debugValues != null) {
+ Iterator<Value> it = debugValues.iterator();
+ while (it.hasNext()) {
+ Value value = it.next();
+ if (value.hasLocalInfo() && value.getLocalInfo() == localInfo) {
+ it.remove();
+ value.removeDebugUser(this);
+ return value;
+ }
+ }
+ }
+ return null;
+ }
+
public void clearDebugValues() {
if (debugValues != null) {
for (Value debugValue : debugValues) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index e365eaa..349d5df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -50,4 +50,12 @@
public Constraint inliningConstraint(AppInfoWithLiveness info, DexType invocationContext) {
return Constraint.ALWAYS;
}
+
+ static String blockNumberToString(BasicBlock block) {
+ try {
+ return "" + block.getNumber();
+ } catch (AssertionError e) {
+ return "<invalid>";
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index ccac8fb..f27153d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -22,7 +22,9 @@
public Move(Value dest, Value src) {
super(dest, src);
- if (src.isNeverNull()) {
+ // CodeRewriter.removeOrReplaceByDebugLocalWrite() might add a Move to a dest that is already
+ // marked never-null. Avoid tripping assertion in markNeverNull() in that case.
+ if (src.isNeverNull() && dest.canBeNull()) {
dest.markNeverNull();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 9e29aae..70d6af4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.cf.LoadStoreHelper;
-import com.android.tools.r8.cf.code.CfPop;
+import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -61,7 +61,7 @@
@Override
public void buildCf(CfBuilder builder) {
- builder.add(new CfPop(inValues.get(0).type));
+ builder.add(CfStackInstruction.popType(inValues.get(0).type));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index e8baacc..50e59f1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfSwitch;
+import com.android.tools.r8.cf.code.CfSwitch.Kind;
import com.android.tools.r8.code.Nop;
import com.android.tools.r8.code.PackedSwitch;
import com.android.tools.r8.code.PackedSwitchPayload;
@@ -265,7 +266,7 @@
builder.append(" ");
builder.append(getKey(i));
builder.append(" -> ");
- builder.append(targetBlock(i).getNumber());
+ builder.append(blockNumberToString(targetBlock(i)));
builder.append("\n");
}
builder.append(" F -> ");
@@ -294,6 +295,6 @@
for (int index : targetBlockIndices) {
labels.add(builder.getLabel(successors.get(index)));
}
- builder.add(new CfSwitch(builder.getLabel(fallthroughBlock()), keys, labels));
+ builder.add(new CfSwitch(Kind.LOOKUP, builder.getLabel(fallthroughBlock()), keys, labels));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 3cfb0c3..272b565 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -68,6 +68,23 @@
throw new Unreachable();
}
}
+
+ static DebugUse join(DebugUse a, DebugUse b) {
+ if (a == LIVE_FINAL || b == LIVE_FINAL) {
+ return LIVE_FINAL;
+ }
+ if (a == b) {
+ return a;
+ }
+ if (a == LIVE) {
+ return b;
+ }
+ if (b == LIVE) {
+ return a;
+ }
+ assert (a == START && b == END) || (a == END && b == START);
+ return LIVE_FINAL;
+ }
}
public static final int UNDEFINED_NUMBER = -1;
@@ -393,9 +410,10 @@
user.replaceOperand(this, newValue);
}
if (debugData != null) {
- for (Instruction user : debugUsers()) {
- user.replaceDebugValue(this, newValue);
+ for (Entry<Instruction, DebugUse> user : debugData.users.entrySet()) {
+ replaceUserInDebugData(user, newValue);
}
+ debugData.users.clear();
for (Phi user : debugPhiUsers()) {
user.replaceDebugValue(this, newValue);
}
@@ -435,12 +453,12 @@
}
}
if (debugData != null) {
- Iterator<Instruction> it = debugData.users.keySet().iterator();
- while (it.hasNext()) {
- Instruction user = it.next();
- if (selectedInstructions.contains(user)) {
- it.remove();
- user.replaceDebugValue(this, newValue);
+ Iterator<Entry<Instruction, DebugUse>> users = debugData.users.entrySet().iterator();
+ while (users.hasNext()) {
+ Entry<Instruction, DebugUse> user = users.next();
+ if (selectedInstructions.contains(user.getKey())) {
+ replaceUserInDebugData(user, newValue);
+ users.remove();
}
}
Iterator<Phi> phis = debugData.phiUsers.iterator();
@@ -454,8 +472,27 @@
}
}
+ private void replaceUserInDebugData(Entry<Instruction, DebugUse> user, Value newValue) {
+ Instruction instruction = user.getKey();
+ DebugUse debugUse = user.getValue();
+ instruction.replaceDebugValue(this, newValue);
+ // If user is a DebugLocalRead and now has no debug values, we would like to remove it.
+ // However, replaceUserInDebugData() is called in contexts where the instruction list is being
+ // iterated, so we cannot remove user from the instruction list at this point.
+ if (newValue.hasLocalInfo()) {
+ DebugUse existing = newValue.debugData.users.get(instruction);
+ assert existing != null;
+ newValue.debugData.users.put(instruction, DebugUse.join(debugUse, existing));
+ }
+ }
+
public void replaceDebugUser(Instruction oldUser, Instruction newUser) {
DebugUse use = debugData.users.remove(oldUser);
+ if (use == DebugUse.START && newUser.outValue == this) {
+ // Register allocation requires that debug values are live at the entry to the instruction.
+ // Remove this debug use since it is starting at the instruction that defines it.
+ return;
+ }
if (use != null) {
newUser.addDebugValue(this);
debugData.users.put(newUser, use);
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..e798fd2 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
@@ -7,9 +7,7 @@
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
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.CfFrame.FrameType;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfPosition;
@@ -263,7 +261,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()) {
@@ -439,51 +438,43 @@
// TODO(zerny): Support having values on the stack on control-edges.
assert stack.isEmpty();
- List<DexType> stackTypes;
+ List<FrameType> stackTypes;
if (block.entry().isMoveException()) {
StackValue exception = (StackValue) block.entry().outValue();
- stackTypes = Collections.singletonList(exception.getObjectType());
+ stackTypes = Collections.singletonList(FrameType.initialized(exception.getObjectType()));
} else {
stackTypes = Collections.emptyList();
}
Collection<Value> locals = registerAllocator.getLocalsAtBlockEntry(block);
- Int2ReferenceSortedMap<DexType> mapping = new Int2ReferenceAVLTreeMap<>();
- Int2ReferenceSortedMap<Uninitialized> allocators = new Int2ReferenceAVLTreeMap<>();
+ Int2ReferenceSortedMap<FrameType> mapping = new Int2ReferenceAVLTreeMap<>();
for (Value local : locals) {
- DexType type;
- Uninitialized allocator = null;
- switch (local.outType()) {
- case INT:
- type = factory.intType;
- break;
- case FLOAT:
- type = factory.floatType;
- break;
- case LONG:
- type = factory.longType;
- break;
- case DOUBLE:
- type = factory.doubleType;
- break;
- case OBJECT:
- type = types.get(local);
- allocator = findAllocator(block, local);
- break;
- default:
- throw new Unreachable(
- "Unexpected local type: " + local.outType() + " for local: " + local);
- }
- mapping.put(getLocalRegister(local), type);
- if (allocator != null) {
- allocators.put(getLocalRegister(local), allocator);
- }
+ mapping.put(getLocalRegister(local), getFrameType(block, local));
}
- instructions.add(new CfFrame(mapping, allocators, stackTypes));
+ instructions.add(new CfFrame(mapping, stackTypes));
}
- private Uninitialized findAllocator(BasicBlock liveBlock, Value value) {
+ private FrameType getFrameType(BasicBlock liveBlock, Value local) {
+ switch (local.outType()) {
+ case INT:
+ return FrameType.initialized(factory.intType);
+ case FLOAT:
+ return FrameType.initialized(factory.floatType);
+ case LONG:
+ return FrameType.initialized(factory.longType);
+ case DOUBLE:
+ return FrameType.initialized(factory.doubleType);
+ case OBJECT:
+ FrameType type = findAllocator(liveBlock, local);
+ return type != null ? type : FrameType.initialized(types.get(local));
+ default:
+ throw new Unreachable(
+ "Unexpected local type: " + local.outType() + " for local: " + local);
+ }
+ }
+
+ private FrameType findAllocator(BasicBlock liveBlock, Value value) {
Instruction definition = value.definition;
while (definition != null && (definition.isStore() || definition.isLoad())) {
definition = definition.inValues().get(0).definition;
@@ -491,13 +482,13 @@
if (definition == null) {
return null;
}
- Uninitialized res;
+ FrameType res;
if (definition.isNewInstance()) {
- res = new UninitializedNew(newInstanceLabels.get(definition.asNewInstance()));
+ res = FrameType.uninitializedNew(newInstanceLabels.get(definition.asNewInstance()));
} else if (definition.isArgument()
&& method.isInstanceInitializer()
&& definition.outValue().isThis()) {
- res = new UninitializedThis();
+ res = FrameType.uninitializedThis();
} else {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 182bb49..8201562 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -420,10 +420,6 @@
return registerAllocator.getArgumentOrAllocateRegisterForValue(value, instructionNumber);
}
- public boolean argumentValueUsesHighRegister(Value value, int instructionNumber) {
- return registerAllocator.argumentValueUsesHighRegister(value, instructionNumber);
- }
-
public void addGoto(com.android.tools.r8.ir.code.Goto jump) {
if (jump.getTarget() != nextBlock) {
add(jump, new GotoInfo(jump));
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 9c6bb86..0d87723 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -120,7 +120,13 @@
}
@Override
- public DebugLocalInfo getCurrentLocal(int register) {
+ public DebugLocalInfo getIncomingLocal(int register) {
+ // TODO(zerny): Support locals in the dex front-end. b/36378142
+ return null;
+ }
+
+ @Override
+ public DebugLocalInfo getOutgoingLocal(int register) {
// TODO(zerny): Support locals in the dex front-end. b/36378142
return null;
}
@@ -166,12 +172,6 @@
}
@Override
- public void closingCurrentBlockWithFallthrough(
- int fallthroughInstructionIndex, IRBuilder builder) {
- // Intentionally empty.
- }
-
- @Override
public void buildInstruction(
IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
throws ApiLevelException {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 5efbfb5..58ed822 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -500,7 +500,6 @@
}
BlockInfo info = targets.get(source.instructionOffset(i));
if (info != null && info.block != currentBlock) {
- source.closingCurrentBlockWithFallthrough(i, this);
closeCurrentBlockWithFallThrough(info.block);
addToWorklist(info.block, i);
break;
@@ -549,20 +548,20 @@
}
public void addThisArgument(int register) {
- DebugLocalInfo local = getCurrentLocal(register);
+ DebugLocalInfo local = getOutgoingLocal(register);
Value value = writeRegister(register, ValueType.OBJECT, ThrowingInfo.NO_THROW, local);
addInstruction(new Argument(value));
value.markAsThis();
}
public void addNonThisArgument(int register, ValueType valueType) {
- DebugLocalInfo local = getCurrentLocal(register);
+ DebugLocalInfo local = getOutgoingLocal(register);
Value value = writeRegister(register, valueType, ThrowingInfo.NO_THROW, local);
addInstruction(new Argument(value));
}
public void addBooleanNonThisArgument(int register) {
- DebugLocalInfo local = getCurrentLocal(register);
+ DebugLocalInfo local = getOutgoingLocal(register);
Value value = writeRegister(register, ValueType.INT, ThrowingInfo.NO_THROW, local);
value.setKnownToBeBoolean(true);
addInstruction(new Argument(value));
@@ -585,10 +584,10 @@
addInstruction(write);
}
- private Value getLocalValue(int register, DebugLocalInfo local) {
+ private Value getIncomingLocalValue(int register, DebugLocalInfo local) {
assert options.debug;
assert local != null;
- assert local == getCurrentLocal(register);
+ assert local == getIncomingLocal(register);
ValueType valueType = ValueType.fromDexType(local.type);
return readRegisterIgnoreLocal(register, valueType);
}
@@ -603,7 +602,7 @@
if (!options.debug) {
return;
}
- Value value = getLocalValue(register, local);
+ Value value = getIncomingLocalValue(register, local);
if (isValidFor(value, local)) {
debugLocalReads.add(value);
}
@@ -613,40 +612,42 @@
if (!options.debug) {
return;
}
- Value value = getLocalValue(register, local);
+ assert local != null;
+ assert local == getOutgoingLocal(register);
+ ValueType valueType = ValueType.fromDexType(local.type);
+ Value incomingValue = readRegisterIgnoreLocal(register, valueType);
- // If the value is for a different local, introduce the new local. We cannot shortcut if the
- // local is defined by a phi as it could end up being trivial.
- if (value.isPhi() || value.getLocalInfo() != local) {
- addDebugLocalWrite(ValueType.fromDexType(local.type), register, value);
+ // TODO(mathiasr): This can be simplified once trivial phi removal is local-info aware.
+ if (incomingValue.isPhi() || incomingValue.getLocalInfo() != local) {
+ addDebugLocalWrite(ValueType.fromDexType(local.type), register, incomingValue);
return;
}
+ assert incomingValue.getLocalInfo() == local;
+ assert !incomingValue.isUninitializedLocal();
- if (!isValidFor(value, local)) {
- return;
- }
-
- // When inserting a start there are two possibilities:
+ // When inserting a start there are three possibilities:
// 1. The block is empty (eg, instructions from block entry until now materialized to nothing).
- // 2. The block is non-empty (and the last instruction does not define the local to start).
+ // 2. The block is non-empty and the last instruction defines the local to start.
+ // 3. The block is non-empty and the last instruction does not define the local to start.
if (currentBlock.getInstructions().isEmpty()) {
addInstruction(new DebugLocalRead());
}
Instruction instruction = currentBlock.getInstructions().getLast();
- assert instruction.outValue() != value;
- instruction.addDebugValue(value);
- value.addDebugLocalStart(instruction);
+ if (instruction.outValue() == incomingValue) {
+ return;
+ }
+ instruction.addDebugValue(incomingValue);
+ incomingValue.addDebugLocalStart(instruction);
}
public void addDebugLocalEnd(int register, DebugLocalInfo local) {
if (!options.debug) {
return;
}
- Value value = getLocalValue(register, local);
+ Value value = getIncomingLocalValue(register, local);
if (!isValidFor(value, local)) {
return;
}
-
// When inserting an end there are three possibilities:
// 1. The block is empty (eg, instructions from block entry until now materialized to nothing).
// 2. The block has an instruction not defining the local being ended.
@@ -862,7 +863,7 @@
Value in = readRegister(src, type);
if (options.debug) {
// If the move is writing to a different local we must construct a new value.
- DebugLocalInfo destLocal = getCurrentLocal(dest);
+ DebugLocalInfo destLocal = getOutgoingLocal(dest);
if (destLocal != null && destLocal != in.getLocalInfo()) {
addDebugLocalWrite(type, dest, in);
return;
@@ -1573,7 +1574,7 @@
// Value abstraction methods.
public Value readRegister(int register, ValueType type) {
- DebugLocalInfo local = getCurrentLocal(register);
+ DebugLocalInfo local = getIncomingLocal(register);
Value value = readRegister(register, type, currentBlock, EdgeType.NON_EDGE, local);
// Check that any information about a current-local is consistent with the read.
if (local != null && value.getLocalInfo() != local && !value.isUninitializedLocal()) {
@@ -1591,8 +1592,8 @@
return value;
}
- public Value readRegisterIgnoreLocal(int register, ValueType type) {
- DebugLocalInfo local = getCurrentLocal(register);
+ private Value readRegisterIgnoreLocal(int register, ValueType type) {
+ DebugLocalInfo local = getIncomingLocal(register);
return readRegister(register, type, currentBlock, EdgeType.NON_EDGE, local);
}
@@ -1691,17 +1692,33 @@
}
public Value writeRegister(int register, ValueType type, ThrowingInfo throwing) {
- DebugLocalInfo local = getCurrentLocal(register);
- previousLocalValue = local == null ? null : readRegisterIgnoreLocal(register, type);
- return writeRegister(register, type, throwing, local);
+ DebugLocalInfo incomingLocal = getIncomingLocal(register);
+ DebugLocalInfo outgoingLocal = getOutgoingLocal(register);
+ // If the local info does not change at the current instruction, we need to ensure
+ // that the old value is read at the instruction by setting 'previousLocalValue'.
+ // If the local info changes, then there must be both an old local ending
+ // and a new local starting at the current instruction, and it is up to the SourceCode
+ // to ensure that the old local is read when it ends.
+ // Furthermore, if incomingLocal != outgoingLocal, then we cannot be sure that
+ // the type of the incomingLocal is the same as the type of the outgoingLocal,
+ // and we must not call readRegisterIgnoreLocal() with the wrong type.
+ previousLocalValue =
+ (incomingLocal == null || incomingLocal != outgoingLocal)
+ ? null
+ : readRegisterIgnoreLocal(register, type);
+ return writeRegister(register, type, throwing, outgoingLocal);
}
public Value writeNumericRegister(int register, NumericType type, ThrowingInfo throwing) {
return writeRegister(register, ValueType.fromNumericType(type), throwing);
}
- private DebugLocalInfo getCurrentLocal(int register) {
- return options.debug ? source.getCurrentLocal(register) : null;
+ private DebugLocalInfo getIncomingLocal(int register) {
+ return options.debug ? source.getIncomingLocal(register) : null;
+ }
+
+ private DebugLocalInfo getOutgoingLocal(int register) {
+ return options.debug ? source.getOutgoingLocal(register) : null;
}
private void checkRegister(int register) {
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/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index b5dfdbb..46cc8e9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -271,6 +271,8 @@
public void buildPrelude(IRBuilder builder) {
currentPosition = getPreamblePosition();
+ state.beginTransactionSynthetic();
+
// Record types for arguments.
Int2ReferenceMap<ValueType> argumentLocals = recordArgumentTypes();
Int2ReferenceMap<ValueType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
@@ -328,13 +330,8 @@
}
}
- // TODO(zerny): This is getting a little out of hands. Clean it up.
-
- // Add debug information for all locals at the initial label.
- List<Local> locals = null;
- if (initialLabel != null) {
- locals = state.openLocals(getOffset(initialLabel));
- }
+ state.endTransaction();
+ state.beginTransaction(0, true);
// Build the actual argument instructions now that type and debug information is known
// for arguments.
@@ -344,13 +341,13 @@
builder.addDebugUninitialized(entry.getIntKey(), entry.getValue());
}
- if (locals != null) {
- for (Local local : locals) {
- if (!argumentLocals.containsKey(local.slot.register)) {
- builder.addDebugLocalStart(local.slot.register, local.info);
- }
+ // Add debug information for all locals at the initial label.
+ for (Local local : state.getLocalsToOpen()) {
+ if (!argumentLocals.containsKey(local.slot.register)) {
+ builder.addDebugLocalStart(local.slot.register, local.info);
}
}
+ state.endTransaction();
if (generateMethodSynchronization()) {
generatingMethodSynchronization = true;
@@ -422,33 +419,36 @@
state.recordStateForTarget(0);
for (JarStateWorklistItem item = worklist.poll(); item != null; item = worklist.poll()) {
state.restoreState(item.instructionIndex);
+ state.beginTransactionAtBlockStart(item.instructionIndex);
+ state.endTransaction();
// Iterate each of the instructions in the block to compute the outgoing JarState.
int instCount = instructionCount();
- for (int i = item.instructionIndex; i <= instCount; ++i) {
- // If we are at the end of the instruction stream or if we have reached the start
- // of a new block, propagate the state to all successors and add the ones
- // that changed to the worklist.
- if (i == instCount || (i != item.instructionIndex && CFG.containsKey(i))) {
- item.blockInfo.normalSuccessors.iterator().forEachRemaining(offset -> {
- if (state.recordStateForTarget(offset)) {
- if (offset >= 0) {
- worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
- }
- }
- });
- item.blockInfo.exceptionalSuccessors.iterator().forEachRemaining(offset -> {
- if (state.recordStateForExceptionalTarget(offset)) {
- if (offset >= 0) {
- worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
- }
- }
- });
- break;
- }
-
+ int blockEnd = item.instructionIndex + 1;
+ while (blockEnd < instCount && !CFG.containsKey(blockEnd)) {
+ blockEnd += 1;
+ }
+ for (int i = item.instructionIndex; i < blockEnd; ++i) {
+ state.beginTransaction(i + 1, i + 1 != blockEnd);
AbstractInsnNode insn = getInstruction(i);
updateState(insn);
+ state.endTransaction();
}
+ // At the end of the current block, propagate the state to all successors and add the ones
+ // that changed to the worklist.
+ item.blockInfo.normalSuccessors.iterator().forEachRemaining(offset -> {
+ if (state.recordStateForTarget(offset)) {
+ if (offset >= 0) {
+ worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
+ }
+ }
+ });
+ item.blockInfo.exceptionalSuccessors.iterator().forEachRemaining(offset -> {
+ if (state.recordStateForExceptionalTarget(offset)) {
+ if (offset >= 0) {
+ worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
+ }
+ }
+ });
}
state.restoreState(0);
}
@@ -477,17 +477,6 @@
}
@Override
- public void closingCurrentBlockWithFallthrough(
- int fallthroughInstructionIndex, IRBuilder builder) {
- AbstractInsnNode insn = node.instructions.get(fallthroughInstructionIndex);
- if (insn instanceof LabelNode) {
- for (Local local : state.getLocalsToClose(getOffset(insn))) {
- builder.addDebugLocalEnd(local.slot.register, local.info);
- }
- }
- }
-
- @Override
public void buildInstruction(
IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
throws ApiLevelException {
@@ -518,7 +507,35 @@
preInstructionState = state.toString();
}
+ if (firstBlockInstruction && insn != initialLabel) {
+ int offset = getOffset(insn);
+ state.beginTransactionAtBlockStart(offset);
+ assert state.getLocalsToClose().isEmpty();
+ for (Local local : state.getLocalsToOpen()) {
+ builder.addDebugLocalStart(local.slot.register, local.info);
+ }
+ state.endTransaction();
+ }
+
+ boolean hasNextInstruction =
+ instructionIndex + 1 != instructionCount()
+ && !builder.getCFG().containsKey(instructionIndex + 1);
+ state.beginTransaction(instructionIndex + 1, hasNextInstruction);
build(insn, builder);
+ if (hasNextInstruction || !isControlFlowInstruction(insn)) {
+ // We're either in straight-line code or at the end of a fallthrough block.
+ // Close locals starting at this point.
+ for (Local local : state.getLocalsToClose()) {
+ builder.addDebugLocalEnd(local.slot.register, local.info);
+ }
+ }
+ if (hasNextInstruction) {
+ // Open the scope of locals starting at this point.
+ for (Local local : state.getLocalsToOpen()) {
+ builder.addDebugLocalStart(local.slot.register, local.info);
+ }
+ }
+ state.endTransaction();
if (Log.ENABLED && !(insn instanceof LineNumberNode)) {
int offset = getOffset(insn);
@@ -555,8 +572,13 @@
}
@Override
- public DebugLocalInfo getCurrentLocal(int register) {
- return generatingMethodSynchronization ? null : state.getLocalInfoForRegister(register);
+ public DebugLocalInfo getIncomingLocal(int register) {
+ return generatingMethodSynchronization ? null : state.getIncomingLocalInfoForRegister(register);
+ }
+
+ @Override
+ public DebugLocalInfo getOutgoingLocal(int register) {
+ return generatingMethodSynchronization ? null : state.getOutgoingLocalInfoForRegister(register);
}
@Override
@@ -1740,14 +1762,7 @@
}
private void updateState(LabelNode insn) {
- int offset = getOffset(insn);
- // Close scope of locals ending at this point.
- List<Local> locals = state.getLocalsToClose(offset);
- state.closeLocals(locals);
- // Open the scope of locals starting at this point.
- if (insn != initialLabel) {
- state.openLocals(offset);
- }
+ // Intentionally empty.
}
private void updateState(LdcInsnNode insn) {
@@ -2455,8 +2470,9 @@
} else {
assert Opcodes.ISTORE <= opcode && opcode <= Opcodes.ASTORE;
Slot src = state.pop(expectedType);
- int dest = state.writeLocal(insn.var, src.type);
+ int dest = state.getLocalRegister(insn.var, src.type);
builder.addMove(valueType(src.type), dest, src.register);
+ state.writeLocal(insn.var, src.type);
}
}
@@ -2697,20 +2713,7 @@
}
private void build(LabelNode insn, IRBuilder builder) {
- int offset = getOffset(insn);
- // Close locals starting at this point.
- List<Local> locals = state.getLocalsToClose(offset);
- for (Local local : locals) {
- builder.addDebugLocalEnd(local.slot.register, local.info);
- }
- state.closeLocals(locals);
-
- // Open the scope of locals starting at this point.
- if (insn != initialLabel) {
- for (Local local : state.openLocals(offset)) {
- builder.addDebugLocalStart(local.slot.register, local.info);
- }
- }
+ // Intentionally empty.
}
private void build(LdcInsnNode insn, IRBuilder builder) throws ApiLevelException {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index af09854..99bca1c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Pair;
import com.google.common.base.Equivalence;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
@@ -426,41 +427,108 @@
// Local variable procedures.
- public List<Local> openLocals(int offset) {
- LocalsAtOffset localsAtOffset = localsAtOffsetTable.get(offset);
- if (localsAtOffset == null) {
- return Collections.emptyList();
+ private List<Pair<Integer, Type>> writes = new ArrayList<>();
+ private List<Local> localsToOpen = new ArrayList<>();
+ private List<Local> localsToClose = new ArrayList<>();
+
+ public void beginTransaction(int offset, boolean hasNextInstruction) {
+ getLocalsToClose(offset);
+ if (hasNextInstruction) {
+ getLocalsToOpen(offset);
+ } else {
+ assert localsToOpen.isEmpty();
+ localsToOpen.clear();
}
- ArrayList<Local> locals = new ArrayList<>(localsAtOffset.starts.size());
- for (LocalNodeInfo start : localsAtOffset.starts) {
- locals.add(setLocalInfo(start.node.index, start.type, start.info));
- }
- return locals;
+ assert writes.isEmpty();
+ writes.clear();
}
- public List<Local> getLocalsToClose(int offset) {
+ public void beginTransactionSynthetic() {
+ assert localsToClose.isEmpty();
+ assert localsToOpen.isEmpty();
+ assert writes.isEmpty();
+ writes.clear();
+ }
+
+ public void endTransaction() {
+ closeLocals();
+ applyWrites();
+ openLocals();
+ }
+
+ public void beginTransactionAtBlockStart(int offset) {
+ // If there are locals closing at the start of a block, just ignore them,
+ // since we should have closed them at the end of the predecessor blocks.
+ assert localsToClose.isEmpty();
+ assert writes.isEmpty();
+ getLocalsToOpen(offset);
+ }
+
+ private void applyWrites() {
+ for (Pair<Integer, Type> write : writes) {
+ applyWriteLocal(write.getFirst(), write.getSecond());
+ }
+ writes.clear();
+ }
+
+ private void getLocalsToOpen(int offset) {
+ assert localsToOpen.isEmpty();
LocalsAtOffset localsAtOffset = localsAtOffsetTable.get(offset);
if (localsAtOffset == null) {
- return Collections.emptyList();
+ return;
}
- ArrayList<Local> locals = new ArrayList<>(localsAtOffset.ends.size());
+ for (LocalNodeInfo start : localsAtOffset.starts) {
+ int register = getLocalRegister(start.node.index, start.type);
+ Local existingLocal = getLocalForRegister(register);
+ assert existingLocal != null;
+ Local local = new Local(existingLocal.slot, start.info);
+ localsToOpen.add(local);
+ }
+ }
+
+ private void openLocals() {
+ for (Local local : localsToOpen) {
+ assert local != null;
+ openLocal(local);
+ }
+ localsToOpen.clear();
+ }
+
+ private void getLocalsToClose(int offset) {
+ assert localsToClose.isEmpty();
+ LocalsAtOffset localsAtOffset = localsAtOffsetTable.get(offset);
+ if (localsAtOffset == null) {
+ return;
+ }
for (LocalNodeInfo end : localsAtOffset.ends) {
int register = getLocalRegister(end.node.index, end.type);
Local local = getLocalForRegister(register);
assert local != null;
if (local.info != null) {
- locals.add(local);
+ localsToClose.add(local);
}
}
- return locals;
}
- public void closeLocals(List<Local> localsToClose) {
- for (Local local : localsToClose) {
- assert local != null;
- assert local == getLocalForRegister(local.slot.register);
+ private void closeLocals() {
+ for (Local localToClose : localsToClose) {
+ assert localToClose != null;
+ // Since the instruction preceding this point may have strongly updated the type,
+ // and the localsToClose list may have been generated before the preceding instruction,
+ // we cannot assert that localToClose == local at this point.
+ // We only set the info to null and leave the type as-is.
+ Local local = getLocalForRegister(localToClose.slot.register);
setLocalForRegister(local.slot.register, local.slot.type, null);
}
+ localsToClose.clear();
+ }
+
+ public List<Local> getLocalsToClose() {
+ return localsToClose;
+ }
+
+ public List<Local> getLocalsToOpen() {
+ return localsToOpen;
}
public ImmutableList<Local> getLocals() {
@@ -485,7 +553,7 @@
return Slot.isCategory1(type) ? index + localsSize : index + 2 * localsSize;
}
- public DebugLocalInfo getLocalInfoForRegister(int register) {
+ public DebugLocalInfo getIncomingLocalInfoForRegister(int register) {
if (register >= locals.length) {
return null;
}
@@ -493,6 +561,27 @@
return local == null ? null : local.info;
}
+ public DebugLocalInfo getOutgoingLocalInfoForRegister(int register) {
+ DebugLocalInfo local = getIncomingLocalInfoForRegister(register);
+ if (local != null && localsToClose != null) {
+ for (Local localToClose : localsToClose) {
+ if (localToClose.slot.register == register) {
+ local = null;
+ break;
+ }
+ }
+ }
+ if (local == null && localsToOpen != null) {
+ for (Local localToOpen : localsToOpen) {
+ if (localToOpen.slot.register == register) {
+ local = localToOpen.info;
+ break;
+ }
+ }
+ }
+ return local;
+ }
+
private Local getLocalForRegister(int register) {
return locals[register];
}
@@ -512,24 +601,25 @@
return local;
}
- private Local setLocalInfo(int index, Type type, DebugLocalInfo info) {
- return setLocalInfoForRegister(getLocalRegister(index, type), info);
- }
-
- private Local setLocalInfoForRegister(int register, DebugLocalInfo info) {
- Local existingLocal = getLocalForRegister(register);
+ private void openLocal(Local localToOpen) {
+ int register = localToOpen.slot.register;
+ DebugLocalInfo info = localToOpen.info;
+ Local local = getLocalForRegister(register);
Type type = Type.getType(info.type.toDescriptorString());
- if (!existingLocal.slot.isCompatibleWith(type)) {
+ if (!local.slot.isCompatibleWith(type)) {
throw new InvalidDebugInfoException(
- "Attempt to define local of type " + prettyType(existingLocal.slot.type) + " as " + info);
+ "Attempt to define local of type " + prettyType(local.slot.type) + " as " + info);
}
- Local local = new Local(existingLocal.slot, info);
- locals[register] = local;
- return local;
+ // Only update local info; keep slot type intact.
+ locals[register] = new Local(local.slot, localToOpen.info);
}
-
public int writeLocal(int index, Type type) {
+ writes.add(new Pair<>(index, type));
+ return getLocalRegister(index, type);
+ }
+
+ private void applyWriteLocal(int index, Type type) {
assert nonNullType(type);
Local local = getLocal(index, type);
if (local != null && local.info != null && !local.slot.isCompatibleWith(type)) {
@@ -540,9 +630,8 @@
// scopes of locals. We assume the program to be verified and overwrite if the types mismatch.
if (local == null || !typeEquals(local.slot.type, type)) {
DebugLocalInfo info = local == null ? null : local.info;
- local = setLocal(index, type, info);
+ setLocal(index, type, info);
}
- return local.slot.register;
}
public boolean typeEquals(Type type1, Type type2) {
@@ -599,7 +688,8 @@
public Slot pop(Type type) {
Slot slot = pop();
- assert slot.isCompatibleWith(type);
+ assert slot.isCompatibleWith(type)
+ : "Tried to pop " + prettyType(slot.type) + " as " + prettyType(type);
return slot;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index be1c670..cd8efa7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -22,7 +22,9 @@
int instructionIndex(int instructionOffset);
int instructionOffset(int instructionIndex);
- DebugLocalInfo getCurrentLocal(int register);
+ DebugLocalInfo getIncomingLocal(int register);
+
+ DebugLocalInfo getOutgoingLocal(int register);
Position getCurrentPosition();
@@ -39,8 +41,6 @@
*/
int traceInstruction(int instructionIndex, IRBuilder builder);
- void closingCurrentBlockWithFallthrough(int fallthroughInstructionIndex, IRBuilder builder);
-
// Setup and release resources used temporarily during trace/build.
void setUp();
void clear();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index 8fca809..5ea5d0e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -113,8 +113,10 @@
DexItemFactory factory = factory();
if (a.isArrayType()) {
- // Arrays are only adaptable to java.lang.Object.
- return b == factory.objectType;
+ // Arrays are only adaptable to java.lang.Object or other arrays, note that we
+ // don't check element type inheritance in the second case since we assume the
+ // input code is verifiable.
+ return b == factory.objectType || b.isArrayType();
}
if (a.isPrimitiveType()) {
@@ -342,8 +344,10 @@
}
}
- if (fromType.isArrayType() && toType == factory().objectType) {
+ if (fromType.isArrayType() && (toType == factory().objectType || toType.isArrayType())) {
// If `fromType` is an array and `toType` is java.lang.Object, no cast is needed.
+ // If both `fromType` and `toType` are arrays, no cast is needed since we assume
+ // the input code is verifiable.
return register;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 16050b2..85a3ac6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -16,6 +17,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue.DexValueBoolean;
import com.android.tools.r8.graph.DexValue.DexValueByte;
@@ -1084,47 +1086,47 @@
if (put.inValue().isConstant()) {
if (put.inValue().isConstNumber()) {
assert put.inValue().isZero();
- encodedField.staticValue = DexValueNull.NULL;
+ encodedField.setStaticValue(DexValueNull.NULL);
} else {
ConstString cnst = put.inValue().getConstInstruction().asConstString();
- encodedField.staticValue = new DexValueString(cnst.getValue());
+ encodedField.setStaticValue(new DexValueString(cnst.getValue()));
}
} else {
InvokeVirtual invoke = put.inValue().definition.asInvokeVirtual();
String name = method.method.getHolder().toSourceString();
if (invoke.getInvokedMethod() == dexItemFactory.classMethods.getSimpleName) {
String simpleName = name.substring(name.lastIndexOf('.') + 1);
- encodedField.staticValue =
- new DexValueString(dexItemFactory.createString(simpleName));
+ encodedField.setStaticValue(
+ new DexValueString(dexItemFactory.createString(simpleName)));
} else {
assert invoke.getInvokedMethod() == dexItemFactory.classMethods.getName;
- encodedField.staticValue = new DexValueString(dexItemFactory.createString(name));
+ encodedField.setStaticValue(new DexValueString(dexItemFactory.createString(name)));
}
}
} else if (field.type.isClassType() || field.type.isArrayType()) {
if (put.inValue().isZero()) {
- encodedField.staticValue = DexValueNull.NULL;
+ encodedField.setStaticValue(DexValueNull.NULL);
} else {
throw new Unreachable("Unexpected default value for field type " + field.type + ".");
}
} else {
ConstNumber cnst = put.inValue().getConstInstruction().asConstNumber();
if (field.type == dexItemFactory.booleanType) {
- encodedField.staticValue = DexValueBoolean.create(cnst.getBooleanValue());
+ encodedField.setStaticValue(DexValueBoolean.create(cnst.getBooleanValue()));
} else if (field.type == dexItemFactory.byteType) {
- encodedField.staticValue = DexValueByte.create((byte) cnst.getIntValue());
+ encodedField.setStaticValue(DexValueByte.create((byte) cnst.getIntValue()));
} else if (field.type == dexItemFactory.shortType) {
- encodedField.staticValue = DexValueShort.create((short) cnst.getIntValue());
+ encodedField.setStaticValue(DexValueShort.create((short) cnst.getIntValue()));
} else if (field.type == dexItemFactory.intType) {
- encodedField.staticValue = DexValueInt.create(cnst.getIntValue());
+ encodedField.setStaticValue(DexValueInt.create(cnst.getIntValue()));
} else if (field.type == dexItemFactory.longType) {
- encodedField.staticValue = DexValueLong.create(cnst.getLongValue());
+ encodedField.setStaticValue(DexValueLong.create(cnst.getLongValue()));
} else if (field.type == dexItemFactory.floatType) {
- encodedField.staticValue = DexValueFloat.create(cnst.getFloatValue());
+ encodedField.setStaticValue(DexValueFloat.create(cnst.getFloatValue()));
} else if (field.type == dexItemFactory.doubleType) {
- encodedField.staticValue = DexValueDouble.create(cnst.getDoubleValue());
+ encodedField.setStaticValue(DexValueDouble.create(cnst.getDoubleValue()));
} else if (field.type == dexItemFactory.charType) {
- encodedField.staticValue = DexValueChar.create((char) cnst.getIntValue());
+ encodedField.setStaticValue(DexValueChar.create((char) cnst.getIntValue()));
} else {
throw new Unreachable("Unexpected field type " + field.type + ".");
}
@@ -1206,8 +1208,7 @@
&& outValue.isUsed()
&& outValue.numberOfPhiUsers() == 0
&& outValue.uniqueUsers().stream().allMatch(isCheckcastToSubtype)) {
- outValue.replaceUsers(inValue);
- it.removeOrReplaceByDebugLocalRead();
+ removeOrReplaceByDebugLocalWrite(it, inValue, outValue);
continue;
}
@@ -1232,8 +1233,7 @@
if (TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, castTypeLattice)) {
assert outTypeLattice.equals(inTypeLattice);
needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
- outValue.replaceUsers(inValue);
- it.removeOrReplaceByDebugLocalRead();
+ removeOrReplaceByDebugLocalWrite(it, inValue, outValue);
continue;
}
// Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
@@ -1253,9 +1253,21 @@
if (needToRemoveTrivialPhis) {
code.removeAllTrivialPhis();
}
+ it = code.instructionIterator();
assert code.isConsistentSSA();
}
+ private void removeOrReplaceByDebugLocalWrite(
+ InstructionIterator it, Value inValue, Value outValue) {
+ if (outValue.getLocalInfo() != inValue.getLocalInfo() && outValue.hasLocalInfo()) {
+ DebugLocalWrite debugLocalWrite = new DebugLocalWrite(outValue, inValue);
+ it.replaceCurrentInstruction(debugLocalWrite);
+ } else {
+ outValue.replaceUsers(inValue);
+ it.removeOrReplaceByDebugLocalRead();
+ }
+ }
+
private boolean canBeFolded(Instruction instruction) {
return (instruction.isBinop() && instruction.asBinop().canBeFolded()) ||
(instruction.isUnop() && instruction.asUnop().canBeFolded());
@@ -1754,7 +1766,8 @@
}
// TODO(mikaelpeltier) Manage that from and to instruction do not belong to the same block.
- private static boolean hasLineChangeBetween(Instruction from, Instruction to) {
+ private static boolean hasLocalOrLineChangeBetween(
+ Instruction from, Instruction to, DexString localVar) {
if (from.getBlock() != to.getBlock()) {
return true;
}
@@ -1778,6 +1791,11 @@
if (instruction == to) {
return false;
}
+ if (instruction.outValue() != null && instruction.outValue().hasLocalInfo()) {
+ if (instruction.outValue().getLocalInfo().name == localVar) {
+ return true;
+ }
+ }
}
throw new Unreachable();
}
@@ -1793,21 +1811,29 @@
}
}
- InstructionIterator iterator = block.iterator();
+ InstructionListIterator iterator = block.listIterator();
while (iterator.hasNext()) {
+ Instruction prevInstruction = iterator.peekPrevious();
Instruction instruction = iterator.next();
if (instruction.isDebugLocalWrite()) {
assert instruction.inValues().size() == 1;
Value inValue = instruction.inValues().get(0);
+ DebugLocalInfo localInfo = instruction.outValue().getLocalInfo();
+ DexString localName = localInfo.name;
if (!inValue.hasLocalInfo() &&
inValue.numberOfAllUsers() == 1 &&
inValue.definition != null &&
- !hasLineChangeBetween(inValue.definition, instruction)) {
- inValue.setLocalInfo(instruction.outValue().getLocalInfo());
- instruction.moveDebugValues(inValue.definition);
+ !hasLocalOrLineChangeBetween(inValue.definition, instruction, localName)) {
+ inValue.setLocalInfo(localInfo);
instruction.outValue().replaceUsers(inValue);
- instruction.clearDebugValues();
- iterator.remove();
+ Value overwrittenLocal = instruction.removeDebugValue(localInfo);
+ if (overwrittenLocal != null) {
+ inValue.definition.addDebugValue(overwrittenLocal);
+ }
+ if (prevInstruction != null) {
+ instruction.moveDebugValues(prevInstruction);
+ }
+ iterator.removeOrReplaceByDebugLocalRead();
}
}
}
@@ -1832,6 +1858,7 @@
iterator.removeOrReplaceByDebugLocalRead();
return;
}
+ assert next.getLocalInfo().name != write.getLocalInfo().name;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 65afb32..cdfa2b1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -78,7 +78,7 @@
DexEncodedField staticField = appInfo.lookupStaticTarget(field.clazz, field);
if (staticField != null) {
Value value = code.createValue(valueType, instruction.getLocalInfo());
- replacement = staticField.staticValue.asConstInstruction(false, value);
+ replacement = staticField.getStaticValue().asConstInstruction(false, value);
} else {
throw new CompilationError(field.clazz.toSourceString() + "." + field.name.toString() +
" used in assumevalues rule does not exist.");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index cd02b19..e9781b4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -821,7 +821,12 @@
}
@Override
- public DebugLocalInfo getCurrentLocal(int register) {
+ public DebugLocalInfo getIncomingLocal(int register) {
+ return null;
+ }
+
+ @Override
+ public DebugLocalInfo getOutgoingLocal(int register) {
return null;
}
@@ -832,11 +837,6 @@
}
@Override
- public void closingCurrentBlockWithFallthrough(
- int fallthroughInstructionIndex, IRBuilder builder) {
- }
-
- @Override
public void setUp() {
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index 2734ff4..7b3eef0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -5,7 +5,7 @@
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstInstruction;
+import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.DebugLocalsChange;
import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.IRCode;
@@ -266,10 +266,9 @@
// Mapping from register number to const number instructions for this basic block.
// Used to remove redundant const instructions that reloads the same constant into
// the same register.
- Map<Integer, ConstInstruction> registerToConstant = new HashMap<>();
+ Map<Integer, ConstNumber> registerToNumber = new HashMap<>();
MoveEliminator moveEliminator = new MoveEliminator(allocator);
ListIterator<Instruction> iterator = block.getInstructions().listIterator();
- boolean mayNoLongerThrow = false;
while (iterator.hasNext()) {
Instruction current = iterator.next();
if (moveEliminator.shouldBeEliminated(current)) {
@@ -277,30 +276,28 @@
} else if (current.outValue() != null && current.outValue().needsRegister()) {
Value outValue = current.outValue();
int instructionNumber = current.getNumber();
- if (!outValue.hasLocalInfo() && (current.isConstNumber() || current.isConstString())) {
- if (constantSpilledAtDefinition(current.asConstInstruction(), allocator)) {
+ if (outValue.isConstant() && current.isConstNumber()) {
+ if (constantSpilledAtDefinition(current.asConstNumber(), allocator)) {
// Remove constant instructions that are spilled at their definition and are
// therefore unused.
iterator.remove();
- mayNoLongerThrow |= current.instructionTypeCanThrow();
+ continue;
+ }
+ int outRegister = allocator.getRegisterForValue(outValue, instructionNumber);
+ ConstNumber numberInRegister = registerToNumber.get(outRegister);
+ if (numberInRegister != null
+ && numberInRegister.identicalNonValueNonPositionParts(current)) {
+ // This instruction is not needed, the same constant is already in this register.
+ // We don't consider the positions of the two (non-throwing) instructions.
+ iterator.remove();
} else {
- int outRegister = allocator.getRegisterForValue(outValue, instructionNumber);
- ConstInstruction constantInRegister = registerToConstant.get(outRegister);
- if (constantInRegister != null
- && constantInRegister.identicalNonValueNonPositionParts(current)) {
- // This instruction is not needed, the same constant is already in this register.
- // We don't consider the positions of the two (non-throwing) instructions.
- iterator.remove();
- mayNoLongerThrow |= current.instructionTypeCanThrow();
+ // Insert the current constant in the mapping. Make sure to clobber the second
+ // register if wide and register-1 if that defines a wide value.
+ registerToNumber.put(outRegister, current.asConstNumber());
+ if (current.outType().isWide()) {
+ registerToNumber.remove(outRegister + 1);
} else {
- // Insert the current constant in the mapping. Make sure to clobber the second
- // register if wide and register-1 if that defines a wide value.
- registerToConstant.put(outRegister, current.asConstNumber());
- if (current.outType().isWide()) {
- registerToConstant.remove(outRegister + 1);
- } else {
- removeWideConstantCovering(registerToConstant, outRegister);
- }
+ removeWideConstantCovering(registerToNumber, outRegister);
}
}
} else {
@@ -308,40 +305,32 @@
// from the mapping.
int outRegister = allocator.getRegisterForValue(outValue, instructionNumber);
for (int i = 0; i < outValue.requiredRegisters(); i++) {
- registerToConstant.remove(outRegister + i);
+ registerToNumber.remove(outRegister + i);
}
// Check if the first register written is the second part of a wide value. If so
// the wide value is no longer active.
- removeWideConstantCovering(registerToConstant, outRegister);
+ removeWideConstantCovering(registerToNumber, outRegister);
}
}
}
-
- if (mayNoLongerThrow && block.hasCatchHandlers() && !block.canThrow()) {
- block.clearCatchHandlers();
- }
}
}
private static void removeWideConstantCovering(
- Map<Integer, ConstInstruction> registerToConstant, int register) {
- ConstInstruction constant = registerToConstant.get(register - 1);
- if (constant != null && constant.outType().isWide()) {
- registerToConstant.remove(register - 1);
+ Map<Integer, ConstNumber> registerToNumber, int register) {
+ ConstNumber number = registerToNumber.get(register - 1);
+ if (number != null && number.outType().isWide()) {
+ registerToNumber.remove(register - 1);
}
}
private static boolean constantSpilledAtDefinition(
- ConstInstruction constInstruction, LinearScanRegisterAllocator allocator) {
- assert constInstruction.isConstNumber() || constInstruction.isConstString();
- if (constInstruction.outValue().isFixedRegisterValue()) {
+ ConstNumber constNumber, LinearScanRegisterAllocator allocator) {
+ if (constNumber.outValue().isFixedRegisterValue()) {
return false;
}
LiveIntervals definitionIntervals =
- constInstruction
- .outValue()
- .getLiveIntervals()
- .getSplitCovering(constInstruction.getNumber());
+ constNumber.outValue().getLiveIntervals().getSplitCovering(constNumber.getNumber());
return definitionIntervals.isSpilledAndRematerializable(allocator);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index f0f5b36..769c281 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -43,6 +43,8 @@
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -186,6 +188,9 @@
// There are no linked values prior to register allocation.
assert noLinkedValues();
assert code.isConsistentSSA();
+ if (this.code.method.accessFlags.isBridge() && implementationIsBridge(this.code)) {
+ transformBridgeMethod();
+ }
computeNeedsRegister();
insertArgumentMoves();
ImmutableList<BasicBlock> blocks = computeLivenessInformation();
@@ -553,12 +558,6 @@
}
}
- @Override
- public boolean argumentValueUsesHighRegister(Value value, int instructionNumber) {
- return isHighRegister(
- getRegisterForValue(value, instructionNumber) + value.requiredRegisters() - 1);
- }
-
public int highestUsedRegister() {
return registersUsed() - 1;
}
@@ -673,7 +672,7 @@
// const number instructions are for values that can be rematerialized instead of
// spilled.
assert instruction.getNumber() == -1;
- assert instruction.isMove() || instruction.isConstNumber() || instruction.isConstString();
+ assert instruction.isMove() || instruction.isConstNumber();
assert !instruction.isDebugInstruction();
return true;
}
@@ -1928,11 +1927,8 @@
newActive.add(splitChild);
// If the constant is split before its first actual use, mark the constant as being
// spilled. That will allows us to remove it afterwards if it is rematerializable.
- Value intervalsValue = intervals.getValue();
- boolean isRematerializableConstantValue =
- intervalsValue.isConstNumber() || intervalsValue.isConstString();
- if (isRematerializableConstantValue
- && intervals.getStart() == intervalsValue.definition.getNumber()
+ if (intervals.getValue().isConstNumber()
+ && intervals.getStart() == intervals.getValue().definition.getNumber()
&& intervals.getUses().size() == 1) {
intervals.setSpilled(true);
}
@@ -1942,7 +1938,9 @@
LiveIntervals splitOfSplit = splitChild.splitBefore(splitChild.getFirstUse());
splitOfSplit.setRegister(intervals.getRegister());
inactive.add(splitOfSplit);
- } else if (isRematerializableConstantValue) {
+ } else if (intervals.getValue().isConstNumber()) {
+ // TODO(ager): Do this for all constants. Currently we only rematerialize const
+ // number and therefore we only do it for numbers at this point.
splitRangesForSpilledConstant(splitChild, registerNumber);
} else if (intervals.isArgumentInterval()) {
splitRangesForSpilledArgument(splitChild);
@@ -1973,7 +1971,6 @@
// register for as long as possible to avoid further moves.
assert spilled.isSpilled();
assert !spilled.getValue().isConstNumber();
- assert !spilled.getValue().isConstString();
assert !spilled.isLinked() || spilled.isArgumentInterval();
boolean isSpillingToArgumentRegister =
(spilled.isArgumentInterval() || registerNumber < numberOfArgumentRegisters);
@@ -2006,7 +2003,7 @@
// spill we are running low on registers and this constant should get out of the way
// as much as possible.
assert spilled.isSpilled();
- assert spilled.getValue().isConstNumber() || spilled.getValue().isConstString();
+ assert spilled.getValue().isConstNumber();
assert !spilled.isLinked() || spilled.isArgumentInterval();
// Do not split range if constant is reused by one of the eleven following instruction.
int maxGapSize = 11 * INSTRUCTION_NUMBER_DELTA;
@@ -2332,6 +2329,96 @@
code.blocks.forEach(BasicBlock::clearUserInfo);
}
+ // Rewrites casts on the form "lhs = (T) rhs" into "(T) rhs" and replaces the uses of lhs by rhs.
+ // This transformation helps to ensure that we do not insert unnecessary moves in bridge methods
+ // with an invoke-range instruction, since all the arguments to the invoke-range instruction will
+ // be original, consecutive arguments of the enclosing method (and importantly, not values that
+ // have been defined by a check-cast instruction).
+ private void transformBridgeMethod() {
+ assert implementationIsBridge(this.code);
+ BasicBlock entry = this.code.blocks.getFirst();
+ InstructionListIterator iterator = entry.listIterator();
+ // Create a mapping from argument values to their index, while scanning over the arguments.
+ Reference2IntMap<Value> argumentIndices = new Reference2IntArrayMap<>();
+ while (iterator.peekNext().isArgument()) {
+ Value argument = iterator.next().asArgument().outValue();
+ argumentIndices.put(argument, argumentIndices.size());
+ }
+ // Move forward until the invocation.
+ while (!iterator.peekNext().isInvoke()) {
+ iterator.next();
+ }
+ Invoke invokeInstruction = iterator.peekNext().asInvoke();
+ // Determine if all of the arguments can be cast without having to move them into lower
+ // registers.
+ int numberOfRequiredRegisters = numberOfArgumentRegisters;
+ if (invokeInstruction.outValue() != null) {
+ numberOfRequiredRegisters += invokeInstruction.outValue().requiredRegisters();
+ }
+ if (numberOfRequiredRegisters - 1 > Constants.U8BIT_MAX) {
+ return;
+ }
+ // Determine if the arguments are consecutive input arguments.
+ List<Value> arguments = invokeInstruction.arguments();
+ if (arguments.size() >= 1) {
+ int previousArgumentIndex = -1;
+ for (int i = 0; i < arguments.size(); ++i) {
+ Value current = arguments.get(i);
+ if (!current.isArgument()) {
+ current = current.definition.asCheckCast().object();
+ }
+ assert current.isArgument();
+ int currentArgumentIndex = argumentIndices.getInt(current);
+ if (previousArgumentIndex >= 0 && currentArgumentIndex != previousArgumentIndex + 1) {
+ return;
+ }
+ previousArgumentIndex = currentArgumentIndex;
+ }
+ } else {
+ return;
+ }
+
+ // Rewrite all casts before the invocation on the form "lhs = (T) rhs" into "(T) rhs", and
+ // replace the uses of lhs by rhs.
+ while (iterator.peekPrevious().isCheckCast()) {
+ CheckCast castInstruction = iterator.previous().asCheckCast();
+ castInstruction.outValue().replaceUsers(castInstruction.object());
+ castInstruction.setOutValue(null);
+ }
+ }
+
+ // Returns true if the IR for this method consists of zero or more arguments, zero or more casts
+ // of the arguments, a single invocation, an optional cast of the result, and a return (in this
+ // particular order).
+ private static boolean implementationIsBridge(IRCode code) {
+ if (code.blocks.size() > 1) {
+ return false;
+ }
+ InstructionListIterator iterator = code.blocks.getFirst().listIterator();
+ // Move forward to the first instruction after the definition of the arguments.
+ while (iterator.hasNext() && iterator.peekNext().isArgument()) {
+ iterator.next();
+ }
+ // Move forward to the first instruction after the casts.
+ while (iterator.hasNext()
+ && iterator.peekNext().isCheckCast()
+ && iterator.peekNext().asCheckCast().object().isArgument()) {
+ iterator.next();
+ }
+ // Check if there is an invoke instruction followed by an optional cast of the result,
+ // and a return.
+ if (!iterator.hasNext() || !iterator.next().isInvoke()) {
+ return false;
+ }
+ if (iterator.hasNext() && iterator.peekNext().isCheckCast()) {
+ iterator.next();
+ }
+ if (!iterator.hasNext() || !iterator.next().isReturn()) {
+ return false;
+ }
+ return true;
+ }
+
private Value createValue(ValueType type) {
Value value = code.createValue(type, null);
value.setNeedsRegister(true);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index d7c6588..7e398e6 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -99,8 +99,8 @@
if (value.isArgument()) {
return true;
}
- boolean isRematerializableConstantValue = value.isConstNumber() || value.isConstString();
- if (!isRematerializableConstantValue) {
+ // TODO(ager): rematerialize const string as well.
+ if (!value.isConstNumber()) {
return false;
}
// If one of the non-spilled splits uses a register that is higher than U8BIT_MAX we cannot
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index 8fcc6b0..f9d0bec 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -10,7 +10,6 @@
void allocateRegisters(boolean debug);
int registersUsed();
int getRegisterForValue(Value value, int instructionNumber);
- boolean argumentValueUsesHighRegister(Value value, int instructionNumber);
int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber);
InternalOptions getOptions();
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index b1e5e4f..d1fe04c 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstNumber;
-import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.FixedRegisterValue;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -141,12 +140,10 @@
instruction = new Move(to, from);
} else {
assert move.definition.isOutConstant();
- Value to = new FixedRegisterValue(move.definition.outType(), move.dst);
ConstInstruction definition = move.definition.getOutConstantConstInstruction();
if (definition.isConstNumber()) {
+ Value to = new FixedRegisterValue(move.definition.outType(), move.dst);
instruction = new ConstNumber(to, definition.asConstNumber().getRawValue());
- } else if (definition.isConstString()) {
- instruction = new ConstString(to, definition.asConstString().getValue());
} else {
throw new Unreachable("Unexpected definition");
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 5a6cc2d..03206b5 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -133,7 +133,12 @@
}
@Override
- public DebugLocalInfo getCurrentLocal(int register) {
+ public DebugLocalInfo getIncomingLocal(int register) {
+ return null;
+ }
+
+ @Override
+ public DebugLocalInfo getOutgoingLocal(int register) {
return null;
}
@@ -144,11 +149,6 @@
}
@Override
- public final void closingCurrentBlockWithFallthrough(
- int fallthroughInstructionIndex, IRBuilder builder) {
- }
-
- @Override
public final void setUp() {
assert constructors.isEmpty();
prepareInstructions();
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index a7b3f77..e49ad85 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedField;
@@ -32,6 +33,7 @@
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.DexValue.UnknownDexValue;
import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.JarClassFileReader;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier;
import com.android.tools.r8.utils.ExceptionUtils;
@@ -217,10 +219,10 @@
}
private Object getStaticValue(DexEncodedField field) {
- if (!field.accessFlags.isStatic() || field.staticValue == null) {
+ if (!field.accessFlags.isStatic() || !field.hasExplicitStaticValue()) {
return null;
}
- return field.staticValue.asAsmEncodedObject();
+ return field.getStaticValue().asAsmEncodedObject();
}
private void writeField(DexEncodedField field, ClassWriter writer) {
@@ -250,18 +252,31 @@
}
}
writeAnnotations(visitor::visitAnnotation, method.annotations.annotations);
- for (int i = 0; i < method.parameterAnnotations.values.length; i++) {
- final int iFinal = i;
- writeAnnotations(
- (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
- method.parameterAnnotations.values[i].annotations);
- }
+ writeParameterAnnotations(visitor, method.parameterAnnotations);
if (!method.accessFlags.isAbstract() && !method.accessFlags.isNative()) {
writeCode(method.getCode(), visitor);
}
visitor.visitEnd();
}
+ private void writeParameterAnnotations(
+ MethodVisitor visitor, DexAnnotationSetRefList parameterAnnotations) {
+ int missingParameterAnnotations = parameterAnnotations.getMissingParameterAnnotations();
+ for (int i = 0; i < missingParameterAnnotations; i++) {
+ AnnotationVisitor av =
+ visitor.visitParameterAnnotation(i, JarClassFileReader.SYNTHETIC_ANNOTATION, false);
+ if (av != null) {
+ av.visitEnd();
+ }
+ }
+ for (int i = 0; i < parameterAnnotations.values.length; i++) {
+ int parameterIndex = i + missingParameterAnnotations;
+ writeAnnotations(
+ (d, vis) -> visitor.visitParameterAnnotation(parameterIndex, d, vis),
+ parameterAnnotations.values[i].annotations);
+ }
+ }
+
private interface AnnotationConsumer {
AnnotationVisitor visit(String desc, boolean visible);
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index f766061..2fd66d0 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardClassFilter;
@@ -62,13 +63,17 @@
}
private void adaptClassStringsInField(DexEncodedField encodedField) {
- if (!(encodedField.staticValue instanceof DexValueString)) {
+ if (!encodedField.accessFlags.isStatic()) {
return;
}
- DexString original = ((DexValueString) encodedField.staticValue).getValue();
+ DexValue staticValue = encodedField.getStaticValue();
+ if (!(staticValue instanceof DexValueString)) {
+ return;
+ }
+ DexString original = ((DexValueString) staticValue).getValue();
DexString renamed = getRenamedStringLiteral(original);
if (renamed != original) {
- encodedField.staticValue = new DexValueString(renamed);
+ encodedField.setStaticValue(new DexValueString(renamed));
}
}
@@ -123,12 +128,16 @@
}
private void replaceIdentifierNameStringInField(DexEncodedField encodedField) {
- if (!(encodedField.staticValue instanceof DexValueString)) {
+ if (!encodedField.accessFlags.isStatic()) {
return;
}
- DexString original = ((DexValueString) encodedField.staticValue).getValue();
+ DexValue staticValue = encodedField.getStaticValue();
+ if (!(staticValue instanceof DexValueString)) {
+ return;
+ }
+ DexString original = ((DexValueString) staticValue).getValue();
if (original instanceof DexItemBasedString) {
- encodedField.staticValue = new DexValueString(materialize((DexItemBasedString) original));
+ encodedField.setStaticValue(new DexValueString(materialize((DexItemBasedString) original)));
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index f6e087b..57e8514 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstString;
@@ -61,13 +62,17 @@
if (!identifierNameStrings.contains(encodedField.field)) {
return;
}
- if (!(encodedField.staticValue instanceof DexValueString)) {
+ if (!encodedField.accessFlags.isStatic()) {
return;
}
- DexString original = ((DexValueString) encodedField.staticValue).getValue();
+ DexValue staticValue = encodedField.getStaticValue();
+ if (!(staticValue instanceof DexValueString)) {
+ return;
+ }
+ DexString original = ((DexValueString) staticValue).getValue();
DexItemBasedString itemBasedString = inferMemberOrTypeFromNameString(appInfo, original);
if (itemBasedString != null) {
- encodedField.staticValue = new DexValueString(itemBasedString);
+ encodedField.setStaticValue(new DexValueString(itemBasedString));
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index a6a9a11..c68122c 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -93,14 +93,14 @@
@Override
public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context) {
DexMethod previous = previousLense.lookupMethod(method, context);
- DexMethod target = bridgeTargetToBridgeMap.get(previous);
+ DexMethod bridge = bridgeTargetToBridgeMap.get(previous);
// Do not forward calls from a bridge method to itself while the bridge method is still
// a bridge.
- if (target == null ||
- (context.accessFlags.isBridge() && target == context.method)) {
+ if (bridge == null
+ || (context.accessFlags.isBridge() && bridge == context.method)) {
return previous;
} else {
- return target;
+ return bridge;
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index 7c53ff8..1c969f6 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -39,6 +39,7 @@
public class FeatureClassMapping {
HashMap<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
+ boolean usesOnlyExactMappings = true;
HashSet<FeaturePredicate> mappings = new HashSet<>();
@@ -76,6 +77,7 @@
mapping.addMapping(javaType, featureJar.getOutputName());
}
}
+ assert mapping.usesOnlyExactMappings;
return mapping;
}
@@ -93,19 +95,22 @@
}
public String featureForClass(String clazz) throws FeatureMappingException {
- // Todo(ricow): improve performance (e.g., direct lookup of class predicates through hashmap).
- FeaturePredicate bestMatch = null;
- for (FeaturePredicate mapping : mappings) {
- if (mapping.match(clazz)) {
- if (bestMatch == null || bestMatch.predicate.length() < mapping.predicate.length()) {
- bestMatch = mapping;
+ if (usesOnlyExactMappings) {
+ return parsedRules.getOrDefault(clazz, baseName);
+ } else {
+ FeaturePredicate bestMatch = null;
+ for (FeaturePredicate mapping : mappings) {
+ if (mapping.match(clazz)) {
+ if (bestMatch == null || bestMatch.predicate.length() < mapping.predicate.length()) {
+ bestMatch = mapping;
+ }
}
}
+ if (bestMatch == null) {
+ return baseName;
+ }
+ return bestMatch.feature;
}
- if (bestMatch == null) {
- return baseName;
- }
- return bestMatch.feature;
}
private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException {
@@ -140,6 +145,7 @@
parsedRules.put(predicate, feature);
FeaturePredicate featurePredicate = new FeaturePredicate(predicate, feature);
mappings.add(featurePredicate);
+ usesOnlyExactMappings &= featurePredicate.isExactmapping();
}
private void error(String error, int line) throws FeatureMappingException {
@@ -168,13 +174,18 @@
if (isCatchAll) {
this.predicate = "";
} else if (isWildcard) {
- this.predicate = predicate.substring(0, predicate.length() - 2);
+ String packageName = predicate.substring(0, predicate.length() - 2);
+ if (!DescriptorUtils.isValidJavaType(packageName)) {
+ throw new FeatureMappingException(packageName + " is not a valid identifier");
+ }
+ // Prefix of a fully-qualified class name, including a terminating dot.
+ this.predicate = predicate.substring(0, predicate.length() - 1);
} else {
+ if (!DescriptorUtils.isValidJavaType(predicate)) {
+ throw new FeatureMappingException(predicate + " is not a valid identifier");
+ }
this.predicate = predicate;
}
- if (!DescriptorUtils.isValidJavaType(this.predicate) && !isCatchAll) {
- throw new FeatureMappingException(this.predicate + " is not a valid identifier");
- }
this.feature = feature;
}
@@ -187,5 +198,9 @@
return className.equals(predicate);
}
}
+
+ boolean isExactmapping() {
+ return !isWildcard && !isCatchAll;
+ }
}
}
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/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
index 587d947..46cd555 100644
--- a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
+++ b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
@@ -143,6 +143,92 @@
}
}
+ static class B78901754 {
+ public static class A {
+ public final String msg;
+
+ public A(String msg) {
+ this.msg = msg;
+ }
+ }
+
+ public static class B extends A {
+ public B(String msg) {
+ super(msg);
+ }
+ }
+
+ public interface IAA {
+ A[] foo(A[] p);
+ }
+
+ public interface IAB {
+ A[] foo(B[] p);
+ }
+
+ public interface IBA {
+ B[] foo(A[] p);
+ }
+
+ public interface IBB {
+ B[] foo(B[] p);
+ }
+
+ public static A[] fooAA(A[] p) {
+ return new A[]{new A("fooAA")};
+ }
+
+ public static A[] fooBA(B[] p) {
+ return new A[]{new A("fooBA")};
+ }
+
+ public static B[] fooAB(A[] p) {
+ return new B[]{new B("fooAB")};
+ }
+
+ public static B[] fooBB(B[] p) {
+ return new B[]{new B("fooBB")};
+ }
+
+ public static void testAA(IAA i) {
+ System.out.println(i.foo(null)[0].msg);
+ }
+
+ public static void testAB(IAB i) {
+ System.out.println(i.foo(null)[0].msg);
+ }
+
+ public static void testBA(IBA i) {
+ System.out.println(i.foo(null)[0].msg);
+ }
+
+ public static void testBB(IBB i) {
+ System.out.println(i.foo(null)[0].msg);
+ }
+
+ public static void test() {
+ testAA(B78901754::fooAA);
+ testAA(B78901754::fooAB);
+ // testAA(B78901754::fooBA); javac error: incompatible types: A[] cannot be converted to B[]
+ // testAA(B78901754::fooBB); javac error: incompatible types: A[] cannot be converted to B[]
+
+ testAB(B78901754::fooAA);
+ testAB(B78901754::fooAB);
+ testAB(B78901754::fooBA);
+ testAB(B78901754::fooBB);
+
+ // testBA(B78901754::fooAA); javac error: A[] cannot be converted to B[]
+ testBA(B78901754::fooAB);
+ // testBA(B78901754::fooBA); javac error: incompatible types: A[] cannot be converted to B[]
+ // testBA(B78901754::fooBB); javac error: incompatible types: A[] cannot be converted to B[]
+
+ // testBB(B78901754::fooAA); javac error: A[] cannot be converted to B[]
+ testBB(B78901754::fooAB);
+ // testBB(B78901754::fooBA); javac error: A[] cannot be converted to B[]
+ testBB(B78901754::fooBB);
+ }
+ }
+
interface B38257037_I1 {
default Number getNumber() {
return new Integer(1);
@@ -420,5 +506,6 @@
B38308515.test();
B38302860.test();
B62168701.test();
+ B78901754.test();
}
}
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..18c3436
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -0,0 +1,240 @@
+// 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("switches.Switches"),
+ makeTest("sync.Sync"),
+ 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"),
+ makeTest("regress_62300145.Regress"),
+ makeTest("regress_64881691.Regress"),
+ makeTest("regress_65104300.Regress"),
+ makeTest("regress_70703087.Test"),
+ makeTest("regress_70736958.Test"),
+ makeTest("regress_70737019.Test"),
+ makeTest("regress_72361252.Test"),
+ makeTest("memberrebinding2.Memberrebinding"),
+ makeTest("memberrebinding3.Memberrebinding"),
+ makeTest("minification.Minification"),
+ makeTest("enclosingmethod.Main"),
+ makeTest("enclosingmethod_proguarded.Main"),
+ makeTest("interfaceinlining.Main"),
+ 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);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
new file mode 100644
index 0000000..5c4a79e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -0,0 +1,457 @@
+// 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.bridgeremoval;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.code.InvokeVirtualRange;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Test;
+
+public class B77836766 extends TestBase {
+
+ /**
+ * The below Jasmin code mimics the following Kotlin code:
+ *
+ * package java_pkg;
+ *
+ * public interface Itf1 {
+ * void foo(String arg);
+ * }
+ *
+ * public interface Itf2 {
+ * void foo(Integer arg);
+ * }
+ *
+ * package kt_pkg;
+ *
+ * internal abstract class AbsCls<T> {
+ * void foo(T obj) { ... }
+ * }
+ *
+ * internal class Cls1() : AbsCls<String>(), Itf1
+ *
+ * internal class Cls2() : AbsCls<Integer>(), Itf2
+ *
+ *
+ * kotlinc introduced bridge methods Cls?#foo to AbsCls#foo:
+ *
+ * class Cls1 extends AbsCls implements Itf1 {
+ * public bridge synthetic void foo(String arg) {
+ * invoke-virtual Cls1#foo(Object)V
+ * }
+ * }
+ *
+ * Note that we can't write such code in Java because javac requires Itf?#foo, which are
+ * technically abstract methods, to be explicitly overridden.
+ */
+ @Test
+ public void test_bridgeTargetInBase_differentBridges() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+
+ ClassBuilder absCls = jasminBuilder.addClass("AbsCls");
+ absCls.setAccess("public abstract");
+ absCls.addFinalMethod("foo", ImmutableList.of("Ljava/lang/Object;"), "V",
+ ".limit stack 3",
+ ".limit locals 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "aload_1",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+ "return");
+
+ ClassBuilder itf1 = jasminBuilder.addInterface("Itf1");
+ itf1.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V");
+
+ ClassBuilder cls1 = jasminBuilder.addClass("Cls1", absCls.name, itf1.name);
+ // Mimic Kotlin's "internal" class
+ cls1.setAccess("");
+ cls1.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/String;"), "V",
+ ".limit stack 2",
+ ".limit locals 2",
+ "aload_0",
+ "aload_1",
+ "invokevirtual " + cls1.name + "/foo(Ljava/lang/Object;)V",
+ "return");
+
+ ClassBuilder itf2 = jasminBuilder.addInterface("Itf2");
+ itf2.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V");
+
+ ClassBuilder cls2 = jasminBuilder.addClass("Cls2", absCls.name, itf2.name);
+ // Mimic Kotlin's "internal" class
+ cls2.setAccess("");
+ cls2.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V",
+ ".limit stack 2",
+ ".limit locals 2",
+ "aload_0",
+ "aload_1",
+ "invokevirtual " + cls2.name + "/foo(Ljava/lang/Object;)V",
+ "return");
+
+ ClassBuilder mainClass = jasminBuilder.addClass("Main");
+ mainClass.addMainMethod(
+ ".limit stack 5",
+ ".limit locals 2",
+ "new " + cls1.name,
+ "dup",
+ "invokespecial " + cls1.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "ldc \"Hello\"",
+ "invokevirtual " + cls1.name + "/foo(Ljava/lang/String;)V",
+ "new " + cls2.name,
+ "dup",
+ "invokespecial " + cls2.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "iconst_0",
+ "invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;",
+ "invokevirtual " + cls2.name + "/foo(Ljava/lang/Integer;)V",
+ "return"
+ );
+
+ final String mainClassName = mainClass.name;
+ String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+
+ AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+
+ DexInspector inspector = new DexInspector(processedApp);
+ ClassSubject absSubject = inspector.clazz(absCls.name);
+ assertThat(absSubject, isPresent());
+ ClassSubject cls1Subject = inspector.clazz(cls1.name);
+ assertThat(cls1Subject, isPresent());
+ ClassSubject cls2Subject = inspector.clazz(cls2.name);
+ assertThat(cls2Subject, isPresent());
+
+ // Cls1#foo and Cls2#foo should not refer to each other.
+ // They can invoke their own bridge method or AbsCls#foo (via member rebinding).
+
+ MethodSubject fooInCls2 =
+ cls2Subject.method("void", "foo", ImmutableList.of("java.lang.Integer"));
+ assertThat(fooInCls2, isPresent());
+ DexCode code = fooInCls2.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(
+ InvokeVirtualRange.class,
+ ReturnVoid.class));
+ InvokeVirtualRange invoke = (InvokeVirtualRange) code.instructions[0];
+ assertEquals(absSubject.getDexClass().type, invoke.getMethod().getHolder());
+
+ MethodSubject fooInCls1 =
+ cls1Subject.method("void", "foo", ImmutableList.of("java.lang.String"));
+ assertThat(fooInCls1, isPresent());
+ code = fooInCls1.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(
+ InvokeVirtualRange.class,
+ ReturnVoid.class));
+ invoke = (InvokeVirtualRange) code.instructions[0];
+ assertEquals(absSubject.getDexClass().type, invoke.getMethod().getHolder());
+ }
+
+ /**
+ * class Base {
+ * void foo(Object o) {...}
+ * }
+ * interface ItfInteger {
+ * void foo(Integer o);
+ * }
+ * class DerivedInteger extends Base implements ItfInteger {
+ * // Bridge method deferring to Base#foo(Object):
+ * public bridge synthetic void foo(Integer o) {
+ * foo((Object) o);
+ * } }
+ * class DerivedString extends Base {
+ * // Regular non-bridge method calling Base#foo(Object):
+ * public void bar(String o) {
+ * foo(o);
+ * } }
+ */
+ @Test
+ public void test_bridgeTargetInBase_bridgeAndNonBridge() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+
+ ClassBuilder baseCls = jasminBuilder.addClass("Base");
+ baseCls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/Object;"), "V",
+ ".limit stack 3",
+ ".limit locals 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "aload_1",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+ "return");
+
+ ClassBuilder itf = jasminBuilder.addInterface("ItfInteger");
+ itf.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V");
+
+ ClassBuilder cls1 = jasminBuilder.addClass("DerivedInteger", baseCls.name, itf.name);
+ cls1.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V",
+ ".limit stack 2",
+ ".limit locals 2",
+ "aload_0",
+ "aload_1",
+ "invokevirtual " + cls1.name + "/foo(Ljava/lang/Object;)V",
+ "return");
+
+ ClassBuilder cls2 = jasminBuilder.addClass("DerivedString", baseCls.name);
+ cls2.addVirtualMethod("bar", ImmutableList.of("Ljava/lang/String;"), "V",
+ ".limit stack 2",
+ ".limit locals 2",
+ "aload_0",
+ "aload_1",
+ "invokevirtual " + cls2.name + "/foo(Ljava/lang/Object;)V",
+ "return");
+
+ ClassBuilder mainClass = jasminBuilder.addClass("Main");
+ mainClass.addMainMethod(
+ ".limit stack 5",
+ ".limit locals 2",
+ "new " + cls1.name,
+ "dup",
+ "invokespecial " + cls1.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "iconst_0",
+ "invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;",
+ "invokevirtual " + cls1.name + "/foo(Ljava/lang/Integer;)V",
+ "new " + cls2.name,
+ "dup",
+ "invokespecial " + cls2.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "ldc \"Bar\"",
+ "invokevirtual " + cls2.name + "/bar(Ljava/lang/String;)V",
+ "return"
+ );
+
+ final String mainClassName = mainClass.name;
+ String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+
+ AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+
+ DexInspector inspector = new DexInspector(processedApp);
+ ClassSubject baseSubject = inspector.clazz(baseCls.name);
+ assertThat(baseSubject, isPresent());
+ ClassSubject cls1Subject = inspector.clazz(cls1.name);
+ assertThat(cls1Subject, isPresent());
+ ClassSubject cls2Subject = inspector.clazz(cls2.name);
+ assertThat(cls2Subject, isPresent());
+
+ // Cls1#foo and Cls2#bar should refer to Base#foo.
+
+ MethodSubject barInCls2 =
+ cls2Subject.method("void", "bar", ImmutableList.of("java.lang.String"));
+ assertThat(barInCls2, isPresent());
+ DexCode code = barInCls2.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(
+ InvokeVirtualRange.class,
+ ReturnVoid.class));
+ InvokeVirtualRange invoke = (InvokeVirtualRange) code.instructions[0];
+ assertEquals(baseSubject.getDexClass().type, invoke.getMethod().getHolder());
+
+ MethodSubject fooInCls1 =
+ cls1Subject.method("void", "foo", ImmutableList.of("java.lang.Integer"));
+ assertThat(fooInCls1, isPresent());
+ code = fooInCls1.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(
+ InvokeVirtualRange.class,
+ ReturnVoid.class));
+ invoke = (InvokeVirtualRange) code.instructions[0];
+ assertEquals(baseSubject.getDexClass().type, invoke.getMethod().getHolder());
+ }
+
+ /**
+ * class Base {
+ * protected void foo(Object o) { ... }
+ * // Bridge method deferring to Base#foo(Object):
+ * public bridge synthetic void foo(Integer o) {
+ * foo((Object) o);
+ * } }
+ * class DerivedString extends Base {
+ * // Regular non-bridge method calling Base#foo(Object):
+ * public void bar(String o) {
+ * foo(o);
+ * } }
+ */
+ @Test
+ public void test_nonBridgeInSubType() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+
+ ClassBuilder baseCls = jasminBuilder.addClass("Base");
+ baseCls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/Object;"), "V",
+ ".limit stack 3",
+ ".limit locals 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "aload_1",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+ "return");
+ baseCls.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V",
+ ".limit stack 2",
+ ".limit locals 2",
+ "aload_0",
+ "aload_1",
+ "invokevirtual " + baseCls.name + "/foo(Ljava/lang/Object;)V",
+ "return");
+
+ ClassBuilder subCls = jasminBuilder.addClass("DerivedString", baseCls.name);
+ subCls.addVirtualMethod("bar", ImmutableList.of("Ljava/lang/String;"), "V",
+ ".limit stack 2",
+ ".limit locals 2",
+ "aload_0",
+ "aload_1",
+ "invokevirtual " + subCls.name + "/foo(Ljava/lang/Object;)V",
+ "return");
+
+ ClassBuilder mainClass = jasminBuilder.addClass("Main");
+ mainClass.addMainMethod(
+ ".limit stack 5",
+ ".limit locals 2",
+ "new " + baseCls.name,
+ "dup",
+ "invokespecial " + baseCls.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "iconst_0",
+ "invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;",
+ "invokevirtual " + baseCls.name + "/foo(Ljava/lang/Integer;)V",
+ "new " + subCls.name,
+ "dup",
+ "invokespecial " + subCls.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "ldc \"Bar\"",
+ "invokevirtual " + subCls.name + "/bar(Ljava/lang/String;)V",
+ "return"
+ );
+
+ final String mainClassName = mainClass.name;
+ String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+
+ AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+
+ DexInspector inspector = new DexInspector(processedApp);
+ ClassSubject baseSubject = inspector.clazz(baseCls.name);
+ assertThat(baseSubject, isPresent());
+ ClassSubject subSubject = inspector.clazz(subCls.name);
+ assertThat(subSubject, isPresent());
+
+ // DerivedString2#bar should refer to Base#foo.
+
+ MethodSubject barInSub =
+ subSubject.method("void", "bar", ImmutableList.of("java.lang.String"));
+ assertThat(barInSub, isPresent());
+ DexCode code = barInSub.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(
+ InvokeVirtualRange.class,
+ ReturnVoid.class));
+ InvokeVirtualRange invoke = (InvokeVirtualRange) code.instructions[0];
+ assertEquals(baseSubject.getDexClass().type, invoke.getMethod().getHolder());
+ }
+
+ /*
+ * public class Base {
+ * public bridge void foo(Integer i) { foo((Object) i); }
+ * public foo(Object o) { print(o); }
+ * public bar(String s) { foo(s); }
+ * }
+ */
+ @Test
+ public void test_bridgeTargetInsideTheSameClass() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+
+ ClassBuilder cls = jasminBuilder.addClass("Base");
+ cls.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V",
+ ".limit stack 2",
+ ".limit locals 2",
+ "aload_0",
+ "aload_1",
+ "invokevirtual " + cls.name + "/foo(Ljava/lang/Object;)V",
+ "return");
+ cls.addVirtualMethod("foo", ImmutableList.of("Ljava/lang/Object;"), "V",
+ ".limit stack 3",
+ ".limit locals 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "aload_1",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+ "return");
+ cls.addVirtualMethod("bar", ImmutableList.of("Ljava/lang/String;"), "V",
+ ".limit stack 2",
+ ".limit locals 2",
+ "aload_0",
+ "aload_1",
+ "invokevirtual " + cls.name + "/foo(Ljava/lang/Object;)V",
+ "return");
+
+ ClassBuilder mainClass = jasminBuilder.addClass("Main");
+ mainClass.addMainMethod(
+ ".limit stack 5",
+ ".limit locals 2",
+ "new " + cls.name,
+ "dup",
+ "invokespecial " + cls.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "iconst_0",
+ "invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;",
+ "invokevirtual " + cls.name + "/foo(Ljava/lang/Integer;)V",
+ "new " + cls.name,
+ "dup",
+ "invokespecial " + cls.name + "/<init>()V",
+ "astore_0",
+ "aload_0",
+ "ldc \"Bar\"",
+ "invokevirtual " + cls.name + "/bar(Ljava/lang/String;)V",
+ "return"
+ );
+ final String mainClassName = mainClass.name;
+ String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+ AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+
+ DexInspector inspector = new DexInspector(processedApp);
+ ClassSubject baseSubject = inspector.clazz(cls.name);
+ assertThat(baseSubject, isPresent());
+
+ // Base#bar should remain as-is, i.e., refer to Base#foo(Object).
+
+ MethodSubject barInSub =
+ baseSubject.method("void", "bar", ImmutableList.of("java.lang.String"));
+ assertThat(barInSub, isPresent());
+ DexCode code = barInSub.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(
+ InvokeVirtualRange.class,
+ ReturnVoid.class));
+ InvokeVirtualRange invoke = (InvokeVirtualRange) code.instructions[0];
+ assertEquals(baseSubject.getDexClass().type, invoke.getMethod().getHolder());
+ }
+
+ private AndroidApp runAndVerifyOnJvmAndArt(
+ JasminBuilder jasminBuilder, String mainClassName, String proguardConfig) throws Exception {
+ // Run input program on java.
+ Path outputDirectory = temp.newFolder().toPath();
+ jasminBuilder.writeClassFiles(outputDirectory);
+ ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
+ assertEquals(0, javaResult.exitCode);
+
+ AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
+ // Disable inlining to avoid the (short) tested method from being inlined then removed.
+ internalOptions -> internalOptions.enableInlining = false);
+
+ // Run processed (output) program on ART
+ ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
+ assertEquals(javaResult.stdout, artResult.stdout);
+ assertEquals(-1, artResult.stderr.indexOf("VerifyError"));
+
+ return processedApp;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTest.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTest.java
index 6e009e8..04b88e6 100644
--- a/src/test/java/com/android/tools/r8/cf/DebugInfoTest.java
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTest.java
@@ -18,8 +18,7 @@
int intVar;
if (arg) {
float floatVar1 = 0f;
- intVar = (int) floatVar1;
- } else {
+ intVar = (int) floatVar1; /* No line break before 'else' to avoid DebugPosition */ } else {
float floatVar2 = 0f;
intVar = (int) floatVar2;
}
diff --git a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
index d371243..45e3c96 100644
--- a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
@@ -4,36 +4,195 @@
package com.android.tools.r8.debug;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.function.Predicate;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.stream.Collectors;
import org.apache.harmony.jpda.tests.framework.jdwp.Value;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class ContinuousSteppingTest extends DebugTestBase {
- private static DebugTestConfig javaD8Config;
- private static DebugTestConfig kotlinD8Config;
+ private static final String MAIN_METHOD_NAME = "main";
+
+ // A list of self-contained jars to process (which do not depend on other jar files).
+ private static final List<Pair<Path, Predicate<Version>>> LIST_OF_JARS = new ConfigListBuilder()
+ .add(DebugTestBase.DEBUGGEE_JAR, ContinuousSteppingTest::allVersions)
+ .add(DebugTestBase.DEBUGGEE_JAVA8_JAR, ContinuousSteppingTest::allVersions)
+ .add(KotlinD8Config.DEBUGGEE_KOTLIN_JAR, ContinuousSteppingTest::allVersions)
+ .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR)),
+ ContinuousSteppingTest::fromAndroidN)
+ .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR)),
+ ContinuousSteppingTest::fromAndroidO)
+ .build();
+
+ private static final Map<Path, DebugTestConfig> compiledJarConfig = new HashMap<>();
+
+ private final String mainClass;
+ private final Path jarPath;
+
+ private static class ConfigListBuilder {
+
+ private final Builder<Pair<Path, Predicate<Version>>> builder = ImmutableList.builder();
+
+ public ConfigListBuilder add(Path path, Predicate<Version> predicate) {
+ builder.add(new Pair<>(path, predicate));
+ return this;
+ }
+
+ public ConfigListBuilder addAll(List<Path> paths, Predicate<Version> predicate) {
+ for (Path path : paths) {
+ add(path, predicate);
+ }
+ return this;
+ }
+
+ public List<Pair<Path, Predicate<Version>>> build() {
+ return builder.build();
+ }
+ }
+
+ public static boolean allVersions(Version dexVmVersion) {
+ return true;
+ }
+
+ public static boolean fromAndroidN(Version dexVmVersion) {
+ return dexVmVersion.isAtLeast(Version.V7_0_0);
+ }
+
+ public static boolean fromAndroidO(Version dexVmVersion) {
+ return dexVmVersion.isAtLeast(Version.DEFAULT);
+ }
+
+ private static List<Path> findAllJarsIn(Path root) {
+ try {
+ return Files.walk(root)
+ .filter(p -> p.toFile().getPath().endsWith(FileUtils.JAR_EXTENSION))
+ .collect(Collectors.toList());
+ } catch (IOException e) {
+ return Collections.emptyList();
+ }
+ }
@BeforeClass
public static void setup() {
- javaD8Config = new D8DebugTestResourcesConfig(temp);
- kotlinD8Config = new KotlinD8Config(temp);
+ LIST_OF_JARS.forEach(pair -> {
+ if (pair.getSecond().test(ToolHelper.getDexVm().getVersion())) {
+ Path jarPath = pair.getFirst();
+ DebugTestConfig config = new D8DebugTestConfig().compileAndAdd(temp, jarPath);
+ compiledJarConfig.put(jarPath, config);
+ }
+ });
+ }
+
+ @Parameters(name = "{0} from {1}")
+ public static Collection<Object[]> getData() throws IOException {
+ List<Object[]> testCases = new ArrayList<>();
+ for (Pair<Path, Predicate<Version>> pair : LIST_OF_JARS) {
+ if (pair.getSecond().test(ToolHelper.getDexVm().getVersion())) {
+ Path jarPath = pair.getFirst();
+ List<String> mainClasses = getAllMainClassesFromJar(jarPath);
+ for (String className : mainClasses) {
+ testCases.add(new Object[]{className, jarPath});
+ }
+ }
+ }
+ return testCases;
+ }
+
+ public ContinuousSteppingTest(String mainClass, Path jarPath) {
+ this.mainClass = mainClass;
+ this.jarPath = jarPath;
}
@Test
- public void testArithmetic() throws Throwable {
- runContinuousTest("Arithmetic", javaD8Config);
+ public void testContinuousSingleStep() throws Throwable {
+ assert compiledJarConfig.containsKey(jarPath);
+ DebugTestConfig config = compiledJarConfig.get(jarPath);
+ assert config != null;
+ runContinuousTest(mainClass, config);
}
- @Test
- public void testLocals() throws Throwable {
- runContinuousTest("Locals", javaD8Config);
+ // Returns a list of classes with a "public static void main(String[])" method in the given jar
+ // file.
+ private static List<String> getAllMainClassesFromJar(Path pathToJar) throws IOException {
+ JarInputStream jarInputStream = new JarInputStream(Files.newInputStream(pathToJar,
+ StandardOpenOption.READ));
+ final URL url = pathToJar.toUri().toURL();
+ assert pathToJar.toFile().exists();
+ assert pathToJar.toFile().isFile();
+ List<String> mainClasses = new ArrayList<>();
+ ClassLoader loader = new URLClassLoader(new URL[]{url},
+ Thread.currentThread().getContextClassLoader());
+
+ try {
+ JarEntry entry;
+ while ((entry = jarInputStream.getNextJarEntry()) != null) {
+ String entryName = entry.getName();
+ if (entryName.endsWith(FileUtils.CLASS_EXTENSION)) {
+ String className =
+ entryName.substring(0, entryName.length() - FileUtils.CLASS_EXTENSION.length());
+ className = className.replace(DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR,
+ DescriptorUtils.JAVA_PACKAGE_SEPARATOR);
+ try {
+ Class<?> cls = loader.loadClass(className);
+ if (cls != null) {
+ long mainMethodsCount = Arrays.stream(cls.getMethods())
+ .filter(ContinuousSteppingTest::isMainMethod)
+ .count();
+ if (mainMethodsCount == 1) {
+ // Add class to the list
+ mainClasses.add(className);
+ }
+ }
+ } catch (Throwable e) {
+ System.out.println(
+ "Could not load class " + className + " from " + pathToJar.toFile().getPath());
+ return Collections.emptyList();
+ }
+ }
+ }
+ } finally {
+ jarInputStream.close();
+ }
+ return mainClasses;
}
- @Test
- public void testKotlinInline() throws Throwable {
- runContinuousTest("KotlinInline", kotlinD8Config);
+ private static boolean isMainMethod(Method m) {
+ return Modifier.isStatic(m.getModifiers())
+ && m.getReturnType() == void.class
+ && m.getName().equals(MAIN_METHOD_NAME)
+ && m.getParameterCount() == 1
+ && m.getParameterTypes()[0] == String[].class;
}
private void runContinuousTest(String debuggeeClassName, DebugTestConfig config)
@@ -41,7 +200,7 @@
runDebugTest(
config,
debuggeeClassName,
- breakpoint(debuggeeClassName, "main"),
+ breakpoint(debuggeeClassName, MAIN_METHOD_NAME),
run(),
stepUntil(StepKind.OVER, StepLevel.INSTRUCTION, debuggeeState -> {
// Fetch local variables.
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTest.java b/src/test/java/com/android/tools/r8/debug/IincDebugTest.java
new file mode 100644
index 0000000..5d65dbb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTest.java
@@ -0,0 +1,17 @@
+// 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.debug;
+
+public class IincDebugTest {
+
+ public static void main(String[] args) {
+ int j;
+ {
+ int i = 1;
+ j = i + 1;
+ }
+ System.out.println(j);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java
new file mode 100644
index 0000000..8ff3d92
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java
@@ -0,0 +1,63 @@
+// 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.debug;
+
+import org.objectweb.asm.*;
+
+public class IincDebugTestDump implements Opcodes {
+
+ public static final String CLASS_NAME = "IincDebugTest";
+ public static final String DESCRIPTOR = "L" + CLASS_NAME + ";";
+
+ public static byte[] dump(int iRegister, int jRegister, boolean useInc) {
+
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
+ MethodVisitor mv;
+
+ cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, CLASS_NAME, null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ Label methodStart = new Label();
+ mv.visitLabel(methodStart);
+ mv.visitLineNumber(12, methodStart);
+ mv.visitInsn(ICONST_1);
+ mv.visitVarInsn(ISTORE, iRegister);
+ Label iStart = new Label();
+ mv.visitLabel(iStart);
+ mv.visitLineNumber(13, iStart);
+ if (useInc) {
+ assert iRegister == jRegister;
+ mv.visitIincInsn(iRegister, 1);
+ } else {
+ mv.visitVarInsn(ILOAD, iRegister);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(IADD);
+ mv.visitVarInsn(ISTORE, jRegister);
+ }
+ Label iEnd = new Label();
+ mv.visitLabel(iEnd);
+ mv.visitLineNumber(15, iEnd);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ILOAD, jRegister);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+ Label l3 = new Label();
+ mv.visitLabel(l3);
+ mv.visitLineNumber(16, l3);
+ mv.visitInsn(RETURN);
+ Label jEnd = new Label();
+ mv.visitLabel(jEnd);
+ mv.visitLocalVariable("i", "I", null, iStart, iEnd, iRegister);
+ mv.visitLocalVariable("args", "[Ljava/lang/String;", null, methodStart, jEnd, 0);
+ mv.visitLocalVariable("j", "I", null, iEnd, jEnd, jRegister);
+ mv.visitMaxs(-1, -1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
new file mode 100644
index 0000000..843a0f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -0,0 +1,130 @@
+// 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.debug;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.stream.Stream;
+import org.junit.Assume;
+import org.junit.Test;
+
+public class IincDebugTestRunner extends DebugTestBase {
+ @Test
+ public void compareDifferentRegister() throws Exception {
+ compareOutput(IincDebugTestDump.dump(1, 2, false));
+ }
+
+ @Test
+ public void compareLoadStoreSameRegister() throws Exception {
+ compareOutput(IincDebugTestDump.dump(1, 1, false));
+ }
+
+ @Test
+ public void compareIinc() throws Exception {
+ compareOutput(IincDebugTestDump.dump(1, 1, true));
+ }
+
+ @Test
+ public void stepDifferentRegister() throws Exception {
+ stepOutput(IincDebugTestDump.dump(1, 2, false));
+ }
+
+ @Test
+ public void stepLoadStoreSameRegister() throws Exception {
+ stepOutput(IincDebugTestDump.dump(1, 1, false));
+ }
+
+ @Test
+ public void stepIinc() throws Exception {
+ stepOutput(IincDebugTestDump.dump(1, 1, true));
+ }
+
+ private void compareOutput(byte[] clazz) throws Exception {
+ Path inputJar = buildInput(clazz);
+ ProcessResult runInput = ToolHelper.runJava(inputJar, IincDebugTestDump.CLASS_NAME);
+ assertEquals(0, runInput.exitCode);
+ ProcessResult runCf = ToolHelper.runJava(buildCf(inputJar), IincDebugTestDump.CLASS_NAME);
+ assertEquals(0, runCf.exitCode);
+ assertEquals(runInput.toString(), runCf.toString());
+ String runDex =
+ ToolHelper.runArtNoVerificationErrors(
+ buildDex(inputJar).toString(), IincDebugTestDump.CLASS_NAME);
+ assertEquals(runInput.stdout, runDex);
+ }
+
+ private void stepOutput(byte[] clazz) throws Exception {
+ // See verifyStateLocation in DebugTestBase.
+ Assume.assumeTrue(
+ "Streaming on Dalvik DEX runtimes has some unknown interference issue",
+ ToolHelper.getDexVm().getVersion().isAtLeast(Version.V6_0_1));
+ Assume.assumeTrue(
+ "Skipping test "
+ + testName.getMethodName()
+ + " because debug tests are not yet supported on Windows",
+ !ToolHelper.isWindows());
+ Path inputJar = buildInput(clazz);
+ new DebugStreamComparator()
+ .add("Input", streamDebugTest(new CfDebugTestConfig(inputJar)))
+ .add("R8/DEX", streamDebugTest(new DexDebugTestConfig(buildDex(inputJar))))
+ .add("R8/CF", streamDebugTest(new CfDebugTestConfig(buildCf(inputJar))))
+ .compare();
+ }
+
+ private Stream<DebuggeeState> streamDebugTest(DebugTestConfig config) throws Exception {
+ return streamDebugTest(config, IincDebugTestDump.CLASS_NAME, ANDROID_FILTER);
+ }
+
+ private Path buildInput(byte[] clazz) {
+ Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+ ArchiveConsumer inputJarConsumer = new ArchiveConsumer(inputJar);
+ inputJarConsumer.accept(clazz, IincDebugTestDump.DESCRIPTOR, null);
+ inputJarConsumer.finished(null);
+ return inputJar;
+ }
+
+ private Path buildDex(Path inputJar) throws Exception {
+ Path dexJar = temp.getRoot().toPath().resolve("r8dex.jar");
+ build(inputJar, new DexIndexedConsumer.ArchiveConsumer(dexJar));
+ return dexJar;
+ }
+
+ private Path buildCf(Path inputJar) throws Exception {
+ Path cfJar = temp.getRoot().toPath().resolve("r8cf.jar");
+ build(inputJar, new ArchiveConsumer(cfJar));
+ return cfJar;
+ }
+
+ private void build(Path inputJar, ProgramConsumer consumer) throws Exception {
+ Builder builder =
+ R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .setProgramConsumer(consumer)
+ .addProgramFiles(inputJar);
+ if ((consumer instanceof ClassFileConsumer)) {
+ builder.addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME));
+ } else {
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
+ }
+ // TODO(b/75997473): Enable inlining when supported by CF backend
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.invalidDebugInfoFatal = true;
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinD8Config.java b/src/test/java/com/android/tools/r8/debug/KotlinD8Config.java
index fa5f5ca..d828a5e 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinD8Config.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinD8Config.java
@@ -17,7 +17,7 @@
*/
class KotlinD8Config extends D8DebugTestConfig {
- private static final Path DEBUGGEE_KOTLIN_JAR =
+ public static final Path DEBUGGEE_KOTLIN_JAR =
Paths.get(ToolHelper.BUILD_DIR, "test", "debug_test_resources_kotlin.jar");
private static AndroidApp compiledResources = null;
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoDump.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoDump.java
new file mode 100644
index 0000000..df03ac3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoDump.java
@@ -0,0 +1,132 @@
+// 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.debuginfo;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class DebugInfoDump implements Opcodes {
+
+ private static final String INTERNAL_NAME = "Foo";
+ public static final String CLASS_NAME = INTERNAL_NAME.replace('/', '.');
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_7, ACC_PUBLIC + ACC_SUPER, INTERNAL_NAME, null, "java/lang/Object", null);
+
+ {
+ fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "f1", "L" + INTERNAL_NAME + ";", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "f2", "Ljava/util/List;", null, null);
+ fv.visitEnd();
+ }
+ foo(cw);
+ main(cw);
+ bar(cw);
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private static void bar(ClassWriter cw) {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "bar", "()I", null, null);
+ mv.visitCode();
+ mv.visitLdcInsn(42);
+ mv.visitInsn(IRETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+
+ private static void main(ClassWriter cw) {
+ MethodVisitor mv =
+ cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+
+ private static void foo(ClassWriter cw) {
+ MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "foo", "()I", null, null);
+ Label[] labels = new Label[11];
+ for (int i = 0; i < labels.length; i++) {
+ labels[i] = new Label();
+ }
+ mv.visitCode();
+ mv.visitLabel(labels[0]);
+ mv.visitLineNumber(10, labels[0]);
+ mv.visitIntInsn(BIPUSH, 12);
+ mv.visitVarInsn(ISTORE, 1);
+ mv.visitLabel(labels[1]);
+ mv.visitLineNumber(11, labels[1]);
+ mv.visitIincInsn(1, 28);
+ mv.visitLabel(labels[2]);
+ mv.visitLineNumber(13, labels[2]);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, INTERNAL_NAME, "f1", "L" + INTERNAL_NAME + ";");
+ mv.visitJumpInsn(IFNULL, labels[3]);
+ mv.visitLabel(labels[4]);
+ mv.visitLineNumber(14, labels[4]);
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, INTERNAL_NAME, "f1", "L" + INTERNAL_NAME + ";");
+ mv.visitMethodInsn(INVOKEVIRTUAL, INTERNAL_NAME, "bar", "()I", false);
+ mv.visitInsn(IADD);
+ mv.visitVarInsn(ISTORE, 1);
+ mv.visitLabel(labels[3]);
+ mv.visitLineNumber(17, labels[3]);
+ mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {Opcodes.INTEGER}, 0, null);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, INTERNAL_NAME, "f2", "Ljava/util/List;");
+ mv.visitJumpInsn(IFNULL, labels[5]);
+ mv.visitLabel(labels[6]);
+ mv.visitLineNumber(18, labels[6]);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, INTERNAL_NAME, "f2", "Ljava/util/List;");
+ mv.visitMethodInsn(
+ INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true);
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitLabel(labels[7]);
+ mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {"java/util/Iterator"}, 0, null);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true);
+ mv.visitJumpInsn(IFEQ, labels[5]);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true);
+ mv.visitTypeInsn(CHECKCAST, INTERNAL_NAME);
+ mv.visitVarInsn(ASTORE, 3);
+ mv.visitLabel(labels[8]);
+ mv.visitLineNumber(19, labels[8]);
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(POP);
+ mv.visitMethodInsn(INVOKESTATIC, INTERNAL_NAME, "foo", "()I", false);
+ mv.visitInsn(IADD);
+ mv.visitVarInsn(ISTORE, 1);
+ mv.visitLabel(labels[9]);
+ mv.visitLineNumber(20, labels[9]);
+ mv.visitJumpInsn(GOTO, labels[7]);
+ mv.visitLabel(labels[5]);
+ mv.visitLineNumber(23, labels[5]);
+ mv.visitFrame(Opcodes.F_CHOP, 1, null, 0, null);
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitInsn(IRETURN);
+ mv.visitLabel(labels[10]);
+ mv.visitLocalVariable("b", "L" + INTERNAL_NAME + ";", null, labels[8], labels[9], 3);
+ mv.visitLocalVariable("this", "L" + INTERNAL_NAME + ";", null, labels[0], labels[10], 0);
+ mv.visitLocalVariable("a", "I", null, labels[1], labels[10], 1);
+ mv.visitMaxs(2, 4);
+ mv.visitEnd();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
new file mode 100644
index 0000000..108b474
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
@@ -0,0 +1,89 @@
+// 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.debuginfo;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Test;
+
+public class KotlinDebugInfoTestRunner extends TestBase {
+ private Path buildInput(byte[] clazz, String descriptor) {
+ Path inputJar = temp.getRoot().toPath().resolve("input.jar");
+ ArchiveConsumer inputJarConsumer = new ArchiveConsumer(inputJar);
+ inputJarConsumer.accept(clazz, descriptor, null);
+ inputJarConsumer.finished(null);
+ return inputJar;
+ }
+
+ private Path buildCf(Path inputJar) throws Exception {
+ Path cfJar = temp.getRoot().toPath().resolve("r8cf.jar");
+ build(inputJar, new ArchiveConsumer(cfJar));
+ return cfJar;
+ }
+
+ @Test
+ public void testRingBuffer() throws Exception {
+ // This test hits the case where we simplify a DebugLocalWrite v'(x) <- v
+ // with debug use [live: y], and y is written between v and v'.
+ // In this case we must not move [live: y] to the definition of v,
+ // since it causes the live range of y to extend to the entry to the first block.
+ test(KotlinRingBufferDump.dump(), KotlinRingBufferDump.CLASS_NAME);
+ }
+
+ @Test
+ public void testReflection() throws Exception {
+ // This test hits the case where we replace a phi(v, v) that has local info
+ // with v that has no local info.
+ test(KotlinReflectionDump.dump(), KotlinReflectionDump.CLASS_NAME);
+ }
+
+ @Test
+ public void testFoo() throws Exception {
+ test(DebugInfoDump.dump(), DebugInfoDump.CLASS_NAME);
+ }
+
+ public void test(byte[] bytes, String className) throws Exception {
+ String descriptor = 'L' + className.replace('.', '/') + ';';
+ Path inputJar = buildInput(bytes, descriptor);
+ ProcessResult runInput = ToolHelper.runJava(inputJar, className);
+ if (0 != runInput.exitCode) {
+ System.out.println(runInput);
+ }
+ assertEquals(0, runInput.exitCode);
+ Path outCf = buildCf(inputJar);
+ ProcessResult runCf = ToolHelper.runJava(outCf, className);
+ assertEquals(runInput.toString(), runCf.toString());
+ }
+
+ private void build(Path inputJar, ProgramConsumer consumer) throws Exception {
+ Builder builder =
+ R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .setProgramConsumer(consumer)
+ .addProgramFiles(inputJar);
+ if ((consumer instanceof ClassFileConsumer)) {
+ builder.addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME));
+ } else {
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
+ }
+ // TODO(b/75997473): Enable inlining when supported by CF backend
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.invalidDebugInfoFatal = true;
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java
new file mode 100644
index 0000000..f8bddbb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java
@@ -0,0 +1,170 @@
+// 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.debuginfo;
+
+import org.objectweb.asm.*;
+
+public class KotlinReflectionDump implements Opcodes {
+
+ private static final String reflectionFactory =
+ "java/lang/Object"; // "kotlin/jvm/internal/ReflectionFactory";
+ private static final String kClass = "java/lang/Object"; // "kotlin/reflect/KClass";
+ private static final String INTERNAL_NAME = "kotlin/jvm/internal/Reflection";
+ public static final String CLASS_NAME = INTERNAL_NAME.replace('/', '.');
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, INTERNAL_NAME, null, "java/lang/Object", null);
+
+ cw.visitSource("Reflection.java", null);
+
+ {
+ fv =
+ cw.visitField(
+ ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
+ "factory",
+ "L" + reflectionFactory + ";",
+ null,
+ null);
+ fv.visitEnd();
+ }
+ {
+ fv =
+ cw.visitField(
+ ACC_FINAL + ACC_STATIC,
+ "REFLECTION_NOT_AVAILABLE",
+ "Ljava/lang/String;",
+ null,
+ " (Kotlin reflection is not available)");
+ fv.visitEnd();
+ }
+ {
+ fv =
+ cw.visitField(
+ ACC_PRIVATE + ACC_FINAL + ACC_STATIC,
+ "EMPTY_K_CLASS_ARRAY",
+ "[L" + kClass + ";",
+ null,
+ null);
+ fv.visitEnd();
+ }
+ method0(cw);
+ mainMethod(cw);
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private static void mainMethod(ClassWriter cw) {
+ MethodVisitor mv =
+ cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+
+ private static void method0(ClassWriter cw) {
+ MethodVisitor mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ Label[] labels = new Label[18];
+ for (int i = 0; i < labels.length; i++) {
+ labels[i] = new Label();
+ }
+ mv.visitCode();
+ mv.visitTryCatchBlock(labels[0], labels[1], labels[2], "java/lang/ClassCastException");
+ mv.visitTryCatchBlock(labels[0], labels[1], labels[3], "java/lang/ClassNotFoundException");
+ mv.visitTryCatchBlock(labels[0], labels[1], labels[4], "java/lang/InstantiationException");
+ mv.visitTryCatchBlock(labels[0], labels[1], labels[5], "java/lang/IllegalAccessException");
+ mv.visitLabel(labels[0]);
+ mv.visitLineNumber(33, labels[0]);
+ mv.visitLdcInsn("kotlin.reflect.jvm.internal.ReflectionFactoryImpl");
+ mv.visitMethodInsn(
+ INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(labels[6]);
+ mv.visitLineNumber(34, labels[6]);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "newInstance", "()Ljava/lang/Object;", false);
+ mv.visitTypeInsn(CHECKCAST, reflectionFactory);
+ mv.visitVarInsn(ASTORE, 0);
+ mv.visitLabel(labels[1]);
+ mv.visitLineNumber(39, labels[1]);
+ mv.visitJumpInsn(GOTO, labels[7]);
+ mv.visitLabel(labels[2]);
+ mv.visitLineNumber(36, labels[2]);
+ mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/ClassCastException"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(labels[8]);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitVarInsn(ASTORE, 0);
+ mv.visitLabel(labels[9]);
+ mv.visitLineNumber(39, labels[9]);
+ mv.visitJumpInsn(GOTO, labels[7]);
+ mv.visitLabel(labels[3]);
+ mv.visitLineNumber(37, labels[3]);
+ mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/ClassNotFoundException"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(labels[10]);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitVarInsn(ASTORE, 0);
+ mv.visitLabel(labels[11]);
+ mv.visitLineNumber(39, labels[11]);
+ mv.visitJumpInsn(GOTO, labels[7]);
+ mv.visitLabel(labels[4]);
+ mv.visitLineNumber(38, labels[4]);
+ mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/InstantiationException"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(labels[12]);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitVarInsn(ASTORE, 0);
+ mv.visitLabel(labels[13]);
+ mv.visitLineNumber(39, labels[13]);
+ mv.visitJumpInsn(GOTO, labels[7]);
+ mv.visitLabel(labels[5]);
+ mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/IllegalAccessException"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(labels[14]);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitVarInsn(ASTORE, 0);
+ mv.visitLabel(labels[7]);
+ mv.visitLineNumber(41, labels[7]);
+ mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] {reflectionFactory}, 0, null);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitJumpInsn(IFNULL, labels[15]);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitJumpInsn(GOTO, labels[16]);
+ mv.visitLabel(labels[15]);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitTypeInsn(NEW, reflectionFactory);
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, reflectionFactory, "<init>", "()V", false);
+ mv.visitLabel(labels[16]);
+ mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {reflectionFactory});
+ mv.visitFieldInsn(PUTSTATIC, INTERNAL_NAME, "factory", "L" + reflectionFactory + ";");
+ mv.visitLabel(labels[17]);
+ mv.visitLineNumber(46, labels[17]);
+ mv.visitInsn(ICONST_0);
+ mv.visitTypeInsn(ANEWARRAY, kClass);
+ mv.visitFieldInsn(PUTSTATIC, INTERNAL_NAME, "EMPTY_K_CLASS_ARRAY", "[L" + kClass + ";");
+ mv.visitInsn(RETURN);
+ mv.visitLocalVariable(
+ "implClass", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", labels[6], labels[1], 1);
+ mv.visitLocalVariable("e", "Ljava/lang/ClassCastException;", null, labels[8], labels[9], 1);
+ mv.visitLocalVariable(
+ "e", "Ljava/lang/ClassNotFoundException;", null, labels[10], labels[11], 1);
+ mv.visitLocalVariable(
+ "e", "Ljava/lang/InstantiationException;", null, labels[12], labels[13], 1);
+ mv.visitLocalVariable(
+ "e", "Ljava/lang/IllegalAccessException;", null, labels[14], labels[7], 1);
+ mv.visitLocalVariable("impl", "L" + reflectionFactory + ";", null, labels[1], labels[17], 0);
+ mv.visitMaxs(2, 2);
+ mv.visitEnd();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java
new file mode 100644
index 0000000..11b981a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java
@@ -0,0 +1,195 @@
+// 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.debuginfo;
+
+import org.objectweb.asm.*;
+
+public class KotlinRingBufferDump implements Opcodes {
+
+ public static final String INTERNAL_NAME = "kotlin/collections/RingBuffer";
+ public static final String DESCRIPTOR = "L" + INTERNAL_NAME + ";";
+ public static final String CLASS_NAME = "kotlin.collections.RingBuffer";
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ AnnotationVisitor av0;
+
+ String superName = "java/lang/Object"; // "kotlin/collections/AbstractList";
+ String signature =
+ null; // "<T:Ljava/lang/Object;>Lkotlin/collections/AbstractList<TT;>;Ljava/util/RandomAccess;";
+ cw.visit(
+ V1_6,
+ ACC_FINAL + ACC_SUPER + ACC_PUBLIC,
+ INTERNAL_NAME,
+ signature,
+ superName,
+ new String[] {"java/util/RandomAccess"});
+
+ {
+ av0 = cw.visitAnnotation("Lkotlin/Metadata;", true);
+ av0.visit("mv", new int[] {1, 1, 9});
+ av0.visit("bv", new int[] {1, 0, 2});
+ av0.visit("k", new Integer(1));
+ {
+ AnnotationVisitor av1 = av0.visitArray("d1");
+ av1.visit(
+ null,
+ "\u0000>\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0008\n\u0002\u0008\u0002\n\u0002\u0010\u0011\n\u0002\u0010\u0000\n\u0002\u0008\u0009\n\u0002\u0010\u0002\n\u0002\u0008\u0006\n\u0002\u0010\u000b\n\u0000\n\u0002\u0010(\n\u0002\u0008\u000c\u0008\u0002\u0018\u0000*\u0004\u0008\u0000\u0010\u00012\u0008\u0012\u0004\u0012\u0002H\u00010\u00022\u00060\u0003j\u0002`\u0004B\r\u0012\u0006\u0010\u0005\u001a\u00020\u0006\u00a2\u0006\u0002\u0010\u0007J\u0013\u0010\u0013\u001a\u00020\u00142\u0006\u0010\u0015\u001a\u00028\u0000\u00a2\u0006\u0002\u0010\u0016J\u0016\u0010\u0017\u001a\u00028\u00002\u0006\u0010\u0018\u001a\u00020\u0006H\u0096\u0002\u00a2\u0006\u0002\u0010\u0019J\u0006\u0010\u001a\u001a\u00020\u001bJ\u000f\u0010\u001c\u001a\u0008\u0012\u0004\u0012\u00028\u00000\u001dH\u0096\u0002J\u000e\u0010\u001e\u001a\u00020\u00142\u0006\u0010\u001f\u001a\u00020\u0006J\u0015\u0010 \u001a\n\u0012\u0006\u0012\u0004\u0018\u00010\n0\u0009H\u0014\u00a2\u0006\u0002\u0010!J'\u0010 \u001a\u0008\u0012\u0004\u0012\u0002H\u00010\u0009\"\u0004\u0008\u0001\u0010\u00012\u000c\u0010\"\u001a\u0008\u0012\u0004\u0012\u0002H\u00010\u0009H\u0015\u00a2\u0006\u0002\u0010#J9\u0010$\u001a\u00020\u0014\"\u0004\u0008\u0001\u0010\u0001*\u0008\u0012\u0004\u0012\u0002H\u00010\u00092\u0006\u0010\u0015\u001a\u0002H\u00012\u0008\u0008\u0002\u0010%\u001a\u00020\u00062\u0008\u0008\u0002\u0010&\u001a\u00020\u0006H\u0002\u00a2\u0006\u0002\u0010'J\u0015\u0010(\u001a\u00020\u0006*\u00020\u00062\u0006\u0010\u001f\u001a\u00020\u0006H\u0083\u0008R\u0018\u0010\u0008\u001a\n\u0012\u0006\u0012\u0004\u0018\u00010\n0\u0009X\u0082\u0004\u00a2\u0006\u0004\n\u0002\u0010\u000bR\u0011\u0010\u0005\u001a\u00020\u0006\u00a2\u0006\u0008\n\u0000\u001a\u0004\u0008\u000c\u0010\rR$\u0010\u000f\u001a\u00020\u00062\u0006\u0010\u000e\u001a\u00020\u0006@RX\u0096\u000e\u00a2\u0006\u000e\n\u0000\u001a\u0004\u0008\u0010\u0010\r\"\u0004\u0008\u0011\u0010\u0007R\u000e\u0010\u0012\u001a\u00020\u0006X\u0082\u000e\u00a2\u0006\u0002\n\u0000\u00a8\u0006)");
+ av1.visitEnd();
+ }
+ {
+ AnnotationVisitor av1 = av0.visitArray("d2");
+ av1.visit(null, DESCRIPTOR);
+ av1.visit(null, "T");
+ av1.visit(null, "Lkotlin/collections/AbstractList;");
+ av1.visit(null, "Ljava/util/RandomAccess;");
+ av1.visit(null, "Lkotlin/collections/RandomAccess;");
+ av1.visit(null, "capacity");
+ av1.visit(null, "");
+ av1.visit(null, "(I)V");
+ av1.visit(null, "buffer");
+ av1.visit(null, "");
+ av1.visit(null, "");
+ av1.visit(null, "[Ljava/lang/Object;");
+ av1.visit(null, "getCapacity");
+ av1.visit(null, "()I");
+ av1.visit(null, "<set-?>");
+ av1.visit(null, "size");
+ av1.visit(null, "getSize");
+ av1.visit(null, "setSize");
+ av1.visit(null, "startIndex");
+ av1.visit(null, "add");
+ av1.visit(null, "");
+ av1.visit(null, "element");
+ av1.visit(null, "(Ljava/lang/Object;)V");
+ av1.visit(null, "get");
+ av1.visit(null, "index");
+ av1.visit(null, "(I)Ljava/lang/Object;");
+ av1.visit(null, "isFull");
+ av1.visit(null, "");
+ av1.visit(null, "iterator");
+ av1.visit(null, "");
+ av1.visit(null, "removeFirst");
+ av1.visit(null, "n");
+ av1.visit(null, "toArray");
+ av1.visit(null, "()[Ljava/lang/Object;");
+ av1.visit(null, "array");
+ av1.visit(null, "([Ljava/lang/Object;)[Ljava/lang/Object;");
+ av1.visit(null, "fill");
+ av1.visit(null, "fromIndex");
+ av1.visit(null, "toIndex");
+ av1.visit(null, "([Ljava/lang/Object;Ljava/lang/Object;II)V");
+ av1.visit(null, "forward");
+ av1.visit(null, "kotlin-stdlib");
+ av1.visitEnd();
+ }
+ av0.visitEnd();
+ }
+ cw.visitInnerClass(
+ INTERNAL_NAME + "$iterator$1", null, null, ACC_PUBLIC + ACC_FINAL + ACC_STATIC);
+
+ {
+ fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "buffer", "[Ljava/lang/Object;", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PRIVATE, "startIndex", "I", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PRIVATE, "size", "I", null, null);
+ fv.visitEnd();
+ }
+ {
+ fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, "capacity", "I", null, null);
+ fv.visitEnd();
+ }
+ methodAdd(cw);
+ methodMain(cw);
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+
+ private static void methodMain(ClassWriter cw) {
+ MethodVisitor mv =
+ cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(0, 1);
+ mv.visitEnd();
+ }
+
+ private static void methodAdd(ClassWriter cw) {
+ MethodVisitor mv =
+ cw.visitMethod(ACC_PUBLIC + ACC_FINAL, "add", "(Ljava/lang/Object;)V", "(TT;)V", null);
+ Label[] labels = new Label[8];
+ for (int i = 0; i < labels.length; i++) {
+ labels[i] = new Label();
+ }
+ mv.visitCode();
+ mv.visitLabel(labels[0]);
+ mv.visitLineNumber(169, labels[0]);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, INTERNAL_NAME, "isFull", "()Z", false);
+ mv.visitJumpInsn(IFEQ, labels[1]);
+ mv.visitLabel(labels[2]);
+ mv.visitLineNumber(170, labels[2]);
+ mv.visitTypeInsn(NEW, "java/lang/IllegalStateException");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("ring buffer is full");
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;)V", false);
+ mv.visitTypeInsn(CHECKCAST, "java/lang/Throwable");
+ mv.visitInsn(ATHROW);
+ mv.visitLabel(labels[1]);
+ mv.visitLineNumber(173, labels[1]);
+ mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, INTERNAL_NAME, "buffer", "[Ljava/lang/Object;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitFieldInsn(GETFIELD, INTERNAL_NAME, "startIndex", "I");
+ mv.visitVarInsn(ISTORE, 3);
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKEVIRTUAL, INTERNAL_NAME, "size", "()I", false);
+ mv.visitVarInsn(ISTORE, 4);
+ mv.visitLabel(labels[3]);
+ mv.visitLineNumber(212, labels[3]);
+ mv.visitVarInsn(ILOAD, 3);
+ mv.visitVarInsn(ILOAD, 4);
+ mv.visitInsn(IADD);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(INVOKEVIRTUAL, INTERNAL_NAME, "getCapacity", "()I", false);
+ mv.visitInsn(IREM);
+ mv.visitLabel(labels[4]);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitInsn(AASTORE);
+ mv.visitLabel(labels[5]);
+ mv.visitLineNumber(174, labels[5]);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKEVIRTUAL, INTERNAL_NAME, "size", "()I", false);
+ mv.visitInsn(DUP);
+ mv.visitVarInsn(ISTORE, 2);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(IADD);
+ mv.visitMethodInsn(INVOKESPECIAL, INTERNAL_NAME, "setSize", "(I)V", false);
+ mv.visitLabel(labels[6]);
+ mv.visitLineNumber(175, labels[6]);
+ mv.visitInsn(RETURN);
+ mv.visitLabel(labels[7]);
+ mv.visitLocalVariable("this_$iv", DESCRIPTOR, null, labels[3], labels[4], 2);
+ mv.visitLocalVariable("$receiver$iv", "I", null, labels[3], labels[4], 3);
+ mv.visitLocalVariable("n$iv", "I", null, labels[3], labels[4], 4);
+ mv.visitLocalVariable("$i$f$forward", "I", null, labels[3], labels[4], 5);
+ mv.visitLocalVariable("this", DESCRIPTOR, null, labels[0], labels[7], 0);
+ mv.visitLocalVariable("element", "Ljava/lang/Object;", null, labels[0], labels[7], 1);
+ mv.visitMaxs(3, 6);
+ mv.visitEnd();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 55195a4..8102882 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -34,11 +34,6 @@
}
@Override
- public boolean argumentValueUsesHighRegister(Value value, int instructionNumber) {
- return false;
- }
-
- @Override
public int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber) {
return value.getNumber();
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index ff3f81a..e5838c3 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -111,6 +111,22 @@
return "L" + name + ";";
}
+ public MethodSignature addAbstractMethod(
+ String name,
+ List<String> argumentTypes,
+ String returnType) {
+ return addMethod("public abstract", name, argumentTypes, returnType);
+ }
+
+ public MethodSignature addFinalMethod(
+ String name,
+ List<String> argumentTypes,
+ String returnType,
+ String... lines) {
+ makeInit = true;
+ return addMethod("public final", name, argumentTypes, returnType, lines);
+ }
+
public MethodSignature addVirtualMethod(
String name,
List<String> argumentTypes,
@@ -238,7 +254,7 @@
isInterface = true;
}
- void setAccess(String access) {
+ public void setAccess(String access) {
this.access = access;
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index a6d0177..50a3c06 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -676,7 +676,12 @@
}
@Override
- public DebugLocalInfo getCurrentLocal(int register) {
+ public DebugLocalInfo getIncomingLocal(int register) {
+ return null;
+ }
+
+ @Override
+ public DebugLocalInfo getOutgoingLocal(int register) {
return null;
}
@@ -686,12 +691,6 @@
}
@Override
- public void closingCurrentBlockWithFallthrough(
- int fallthroughInstructionIndex, IRBuilder builder) {
- throw new Unreachable();
- }
-
- @Override
public void setUp() {
// Intentionally empty.
}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index fa39257..f27c88a 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -303,9 +303,10 @@
private static int countRenamedClassIdentifier(
DexInspector inspector, DexEncodedField[] fields) {
return Arrays.stream(fields)
- .filter(encodedField -> encodedField.staticValue instanceof DexValueString)
+ .filter(encodedField -> encodedField.getStaticValue() instanceof DexValueString)
.reduce(0, (cnt, encodedField) -> {
- String cnstString = ((DexValueString) encodedField.staticValue).getValue().toString();
+ String cnstString =
+ ((DexValueString) encodedField.getStaticValue()).getValue().toString();
if (isValidJavaType(cnstString)) {
ClassSubject classSubject = inspector.clazz(cnstString);
if (classSubject.isRenamed()
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232.java
index eb9bf2e..2e4d6cf 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232.java
@@ -4,8 +4,13 @@
package com.android.tools.r8.regress.b78493232;
import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import org.junit.Assume;
import org.junit.Test;
@@ -21,4 +26,18 @@
Regress78493232Dump.dump(),
ToolHelper.getClassAsBytes(Regress78493232Utils.class));
}
+
+ // Main method to build a test jar for testing on device.
+ public static void main(String[] args) throws CompilationFailedException, IOException {
+ Path output = args.length > 0
+ ? Paths.get(args[0])
+ : Paths.get("Regress78493232.jar");
+ ArchiveConsumer consumer = new ArchiveConsumer(output);
+ consumer.accept(Regress78493232Dump.dump(), Regress78493232Dump.CLASS_DESC, null);
+ consumer.accept(
+ ToolHelper.getClassAsBytes(Regress78493232Utils.class),
+ Regress78493232Dump.UTILS_CLASS_DESC,
+ null);
+ consumer.finished(null);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump.java
index ba7ec6b..8bff3c7 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Dump.java
@@ -117,49 +117,21 @@
mv.visitLdcInsn(new Integer(iterations));
Label l1 = new Label();
mv.visitJumpInsn(IF_ICMPGE, l1);
- {
- mv.visitMethodInsn(INVOKESTATIC, CLASS_INTERNAL, "run", "()Ljava/lang/String;", false);
-
- mv.visitVarInsn(ASTORE, 2);
- mv.visitVarInsn(ALOAD, 2);
- mv.visitLdcInsn("java.security.SecureRandom");
- mv.visitMethodInsn(
- INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
- Label l2 = new Label();
- mv.visitJumpInsn(IFNE, l2);
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
- mv.visitLdcInsn("result incorrect: ");
- mv.visitMethodInsn(
- INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
- mv.visitVarInsn(ALOAD, 2);
- mv.visitMethodInsn(
- INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
- mv.visitInsn(ICONST_1);
- mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "exit", "(I)V", false);
- mv.visitInsn(RETURN);
-
- mv.visitLabel(l2);
- getHash(mv);
- mv.visitLdcInsn(new Integer(419176645));
- Label l3 = new Label();
- mv.visitJumpInsn(IF_ICMPEQ, l3);
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
- mv.visitLdcInsn("state incorrect: ");
- mv.visitMethodInsn(
- INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
- printState(mv);
- mv.visitInsn(ICONST_2);
- mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "exit", "(I)V", false);
- mv.visitInsn(RETURN);
- mv.visitLabel(l3);
- }
+ mv.visitMethodInsn(INVOKESTATIC, CLASS_INTERNAL, "run", "()Ljava/lang/String;", false);
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitMethodInsn(
+ INVOKESTATIC, UTILS_CLASS_INTERNAL, "compare", "(Ljava/lang/String;I)V", false);
+ mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntA", "I");
+ mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntB", "I");
+ mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticByteArray", "[B");
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitMethodInsn(INVOKESTATIC, UTILS_CLASS_INTERNAL, "compareHash", "(II[BI)V", false);
mv.visitIincInsn(1, 1);
mv.visitJumpInsn(GOTO, l0);
mv.visitLabel(l1);
-
- println(mv, "Completed successfully after " + iterations + " iterations");
+ mv.visitLdcInsn("Completed successfully after " + iterations + " iterations");
+ mv.visitMethodInsn(
+ INVOKESTATIC, UTILS_CLASS_INTERNAL, "println", "(Ljava/lang/String;)V", false);
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
@@ -335,20 +307,6 @@
return cw.toByteArray();
}
- private static void printState(MethodVisitor mv) {
- println(mv, "staticIntA:");
- mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntA", "I");
- printlnInt(mv);
-
- println(mv, "staticIntB:");
- mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntB", "I");
- printlnInt(mv);
-
- println(mv, "staticByteArray:");
- mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticByteArray", "[B");
- printByteArray(mv);
- }
-
private static void getHash(MethodVisitor mv) {
mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntA", "I");
mv.visitFieldInsn(GETSTATIC, CLASS_INTERNAL, "staticIntB", "I");
@@ -356,21 +314,4 @@
mv.visitMethodInsn(INVOKESTATIC, UTILS_CLASS_INTERNAL, "getHash", "(II[B)I", false);
}
- private static void printByteArray(MethodVisitor mv) {
- mv.visitMethodInsn(INVOKESTATIC, UTILS_CLASS_INTERNAL, "printByteArray", "([B)V", false);
- }
-
- private static void printlnInt(MethodVisitor mv) {
- mv.visitMethodInsn(INVOKESTATIC, UTILS_CLASS_INTERNAL, "println", "(I)V", false);
- }
-
- private static void printlnString(MethodVisitor mv) {
- mv.visitMethodInsn(
- INVOKESTATIC, UTILS_CLASS_INTERNAL, "println", "(Ljava/lang/String;)V", false);
- }
-
- private static void println(MethodVisitor mv, String msg) {
- mv.visitLdcInsn(msg);
- printlnString(mv);
- }
}
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java
index 14b17ce..40d979e 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232Utils.java
@@ -9,15 +9,7 @@
public class Regress78493232Utils {
- public static void println(String msg) {
- System.out.println(msg);
- }
-
- public static void println(int msg) {
- System.out.println(msg);
- }
-
- public static void printByteArray(byte[] array) {
+ private static void printByteArray(byte[] array) {
List<String> strings = new ArrayList<>(array.length);
for (byte b : array) {
strings.add(Byte.toString(b));
@@ -29,7 +21,30 @@
return a + 7 * b + 13 * Arrays.hashCode(c);
}
- public static void printHash(int a, int b, byte[] c) {
- System.out.println(getHash(a, b, c));
+ public static void compare(String output, int iterations) {
+ String expected = "java.security.SecureRandom";
+ if (output.equals(expected)) {
+ return;
+ }
+ System.out.println(
+ "After " + iterations + " iterations, expected \"" +
+ expected + "\", but got \"" + output + "\"");
+ System.exit(1);
+ }
+
+ public static void compareHash(int a, int b, byte[] c, int iterations) {
+ int expected = 419176645;
+ int output = getHash(a, b, c);
+ if (output == expected) {
+ return;
+ }
+ System.out.println(
+ "After " + iterations + " iterations, expected hash " +
+ expected + ", but got " + output);
+ System.out.println("staticIntA: " + a);
+ System.out.println("staticIntB: " + b);
+ System.out.print("staticIntByteArray: ");
+ printByteArray(c);
+ System.exit(1);
}
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 51772fe..087b5f8 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -100,47 +100,47 @@
inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
DexValue value;
- assertTrue(inspector.clazz("Test").field("boolean", "booleanField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("boolean", "booleanField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("boolean", "booleanField").getStaticValue();
assertTrue(value instanceof DexValueBoolean);
assertEquals(true, ((DexValueBoolean) value).getValue());
- assertTrue(inspector.clazz("Test").field("byte", "byteField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("byte", "byteField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("byte", "byteField").getStaticValue();
assertTrue(value instanceof DexValueByte);
assertEquals(1, ((DexValueByte) value).getValue());
- assertTrue(inspector.clazz("Test").field("short", "shortField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("short", "shortField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("short", "shortField").getStaticValue();
assertTrue(value instanceof DexValueShort);
assertEquals(2, ((DexValueShort) value).getValue());
- assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("int", "intField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("int", "intField").getStaticValue();
assertTrue(value instanceof DexValueInt);
assertEquals(3, ((DexValueInt) value).getValue());
- assertTrue(inspector.clazz("Test").field("long", "longField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("long", "longField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("long", "longField").getStaticValue();
assertTrue(value instanceof DexValueLong);
assertEquals(4, ((DexValueLong) value).getValue());
- assertTrue(inspector.clazz("Test").field("float", "floatField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("float", "floatField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("float", "floatField").getStaticValue();
assertTrue(value instanceof DexValueFloat);
assertEquals(5.0f, ((DexValueFloat) value).getValue(), 0.0);
- assertTrue(inspector.clazz("Test").field("double", "doubleField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("double", "doubleField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("double", "doubleField").getStaticValue();
assertTrue(value instanceof DexValueDouble);
assertEquals(6.0f, ((DexValueDouble) value).getValue(), 0.0);
- assertTrue(inspector.clazz("Test").field("char", "charField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("char", "charField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("char", "charField").getStaticValue();
assertTrue(value instanceof DexValueChar);
assertEquals(0x30 + 7, ((DexValueChar) value).getValue());
- assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
assertTrue(value instanceof DexValueString);
assertEquals(("8"), ((DexValueString) value).getValue().toString());
@@ -317,12 +317,12 @@
inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
DexValue value;
- assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("int", "intField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("int", "intField").getStaticValue();
assertTrue(value instanceof DexValueInt);
assertEquals(3, ((DexValueInt) value).getValue());
- assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
assertTrue(value instanceof DexValueString);
assertEquals(("7"), ((DexValueString) value).getValue().toString());
@@ -388,12 +388,12 @@
assertTrue(inspector.clazz("Test").clinit().isPresent());
DexValue value;
- assertTrue(inspector.clazz("Test").field("int", "intField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("int", "intField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("int", "intField").getStaticValue();
assertTrue(value instanceof DexValueInt);
assertEquals(3, ((DexValueInt) value).getValue());
- assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasStaticValue());
+ assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
assertTrue(value instanceof DexValueString);
assertEquals(("7"), ((DexValueString) value).getValue().toString());
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 0cea762..29676da 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -821,7 +821,7 @@
}
public abstract class FieldSubject extends MemberSubject {
- public abstract boolean hasStaticValue();
+ public abstract boolean hasExplicitStaticValue();
public abstract DexEncodedField getField();
@@ -863,7 +863,7 @@
}
@Override
- public boolean hasStaticValue() {
+ public boolean hasExplicitStaticValue() {
return false;
}
@@ -928,13 +928,13 @@
}
@Override
- public boolean hasStaticValue() {
- return dexField.staticValue != null;
+ public boolean hasExplicitStaticValue() {
+ return isStatic() && dexField.hasExplicitStaticValue();
}
@Override
public DexValue getStaticValue() {
- return dexField.staticValue;
+ return dexField.getStaticValue();
}
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
index eff632a..f41be5c 100644
--- a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
+++ b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
@@ -94,6 +94,7 @@
assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
assertEquals(mapping.featureForClass("com.google.different.Feature1"), "base");
assertEquals(mapping.featureForClass("com.strange.different.Feature1"), "feature2");
+ assertEquals(mapping.featureForClass("com.stranger.Clazz"), "base");
assertEquals(mapping.featureForClass("Feature1"), "base");
assertEquals(mapping.featureForClass("a.b.z.A"), "base");
}
@@ -115,4 +116,17 @@
ensureThrowsMappingException(
ImmutableList.of("com.google.foo.*:feature1", "com.google.foo.*:feature2"));
}
+
+ @Test
+ public void testUsesOnlyExactMappings() throws Exception {
+ List<String> lines =
+ ImmutableList.of(
+ "com.pkg1.Clazz:feature1",
+ "com.pkg2.Clazz:feature2");
+ FeatureClassMapping mapping = new FeatureClassMapping(lines);
+
+ assertEquals(mapping.featureForClass("com.pkg1.Clazz"), "feature1");
+ assertEquals(mapping.featureForClass("com.pkg2.Clazz"), "feature2");
+ assertEquals(mapping.featureForClass("com.pkg1.Other"), mapping.baseName);
+ }
}