Merge "Don't compare positions for non-throwing instructions in release mode."
diff --git a/build.gradle b/build.gradle
index 580bcaf..f5a3337 100644
--- a/build.gradle
+++ b/build.gradle
@@ -512,6 +512,15 @@
baseName 'deps'
}
+task repackageDepsForLib(type: ShadowJar) {
+ configurations = [project.configurations.compile]
+ mergeServiceFiles(it)
+ configureRelocations(it)
+ exclude { it.getRelativePath().getPathString() == "module-info.class" }
+ exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
+ baseName 'r8lib_deps'
+}
+
task repackageSources(type: ShadowJar) {
from sourceSets.main.output
mergeServiceFiles(it)
@@ -521,6 +530,18 @@
baseName 'sources'
}
+task R8libWithDeps(type: ShadowJar) {
+ from consolidatedLicense.outputs.files
+ baseName 'r8lib_with_deps'
+ classifier = null
+ version = null
+ manifest {
+ attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
+ }
+ from repackageSources.outputs.files
+ from repackageDepsForLib.outputs.files
+}
+
task R8(type: ShadowJar) {
from consolidatedLicense.outputs.files
baseName 'r8'
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ec863ee..46f4876 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -476,21 +476,19 @@
ProguardMapSupplier proguardMapSupplier;
- if (options.lineNumberOptimization != LineNumberOptimization.OFF) {
- timing.begin("Line number remapping");
- ClassNameMapper classNameMapper =
- LineNumberOptimizer.run(
- application,
- appView.graphLense(),
- namingLens,
- options.lineNumberOptimization == LineNumberOptimization.IDENTITY_MAPPING);
- timing.end();
- proguardMapSupplier =
- ProguardMapSupplier.fromClassNameMapper(classNameMapper, options.minApiLevel);
- } else {
- proguardMapSupplier =
- ProguardMapSupplier.fromNamingLens(namingLens, application, options.minApiLevel);
- }
+ timing.begin("Line number remapping");
+ // When line number optimization is turned off the identity mapping for line numbers is
+ // used. We still run the line number optimizer to collect line numbers and inline frame
+ // information for the mapping file.
+ ClassNameMapper classNameMapper =
+ LineNumberOptimizer.run(
+ application,
+ appView.graphLense(),
+ namingLens,
+ options.lineNumberOptimization == LineNumberOptimization.OFF);
+ timing.end();
+ proguardMapSupplier =
+ ProguardMapSupplier.fromClassNameMapper(classNameMapper, options.minApiLevel);
// If a method filter is present don't produce output since the application is likely partial.
if (options.hasMethodsFilter()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 1264a26..779678d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -47,7 +47,7 @@
writer.putUleb128(delta);
}
- AdvancePC(int delta) {
+ public AdvancePC(int delta) {
this.delta = delta;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 90a098a..10e960c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -217,6 +217,7 @@
public final LongMethods longMethods = new LongMethods();
public final ThrowableMethods throwableMethods = new ThrowableMethods();
public final ClassMethods classMethods = new ClassMethods();
+ public final EnumMethods enumMethods = new EnumMethods();
public final PrimitiveTypesBoxedTypeFields primitiveTypesBoxedTypeFields =
new PrimitiveTypesBoxedTypeFields();
public final AtomicFieldUpdaterMethods atomicFieldUpdaterMethods =
@@ -408,6 +409,20 @@
}
}
+ public class EnumMethods {
+
+ public DexMethod valueOf;
+
+ private EnumMethods() {
+ valueOf =
+ createMethod(
+ enumDescriptor,
+ valueOfMethodName,
+ enumDescriptor,
+ new DexString[] {classDescriptor, stringDescriptor});
+ }
+ }
+
/**
* All boxed types (Boolean, Byte, ...) have a field named TYPE which contains the Class object
* for the primitive type.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index eed1c47..9d36037 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -108,31 +108,12 @@
// goto A
//
// A: ...y // blockWithNonNullInstruction
- //
+ boolean split = block.hasCatchHandlers();
BasicBlock blockWithNonNullInstruction =
- block.hasCatchHandlers() ? iterator.split(code, blockIterator) : block;
- // Next, add non-null fake IR, e.g.,
- // ...x
- // invoke(rcv, ...)
- // goto A
- // ...
- // A: non_null_rcv <- non-null(rcv)
- // ...y
- Value nonNullValue = code.createValue(
- knownToBeNonNullValue.getTypeLattice(),
- knownToBeNonNullValue.getLocalInfo());
- nonNullValueCollector.add(nonNullValue);
- NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
- nonNull.setPosition(current.getPosition());
- if (blockWithNonNullInstruction != block) {
- // If we split, add non-null IR on top of the new split block.
- blockWithNonNullInstruction.listIterator().add(nonNull);
- } else {
- // Otherwise, just add it to the current block at the position of the iterator.
- iterator.add(nonNull);
- }
- // Then, replace all users of the original value that are dominated by either the current
- // block or the new split-off block. Since NPE can be explicitly caught, nullness should be
+ split ? iterator.split(code, blockIterator) : block;
+
+ // Find all users of the original value that are dominated by either the current block
+ // or the new split-off block. Since NPE can be explicitly caught, nullness should be
// propagated through dominance.
Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
@@ -142,14 +123,13 @@
for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
dominatedBlocks.add(dominatee);
InstructionListIterator dominateeIterator = dominatee.listIterator();
- if (dominatee == blockWithNonNullInstruction) {
- // In the block with the inserted non null instruction, skip instructions up to and
- // including the newly inserted instruction.
- dominateeIterator.nextUntil(instruction -> instruction == nonNull);
+ if (dominatee == blockWithNonNullInstruction && !split) {
+ // In the block where the non null instruction will be inserted, skip instructions up
+ // to and including the insertion point.
+ dominateeIterator.nextUntil(instruction -> instruction == current);
}
while (dominateeIterator.hasNext()) {
Instruction potentialUser = dominateeIterator.next();
- assert potentialUser != nonNull;
if (users.contains(potentialUser)) {
dominatedUsers.add(potentialUser);
}
@@ -162,8 +142,35 @@
dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
}
}
- knownToBeNonNullValue.replaceSelectiveUsers(
- nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+
+ // Only insert non-null instruction if it is ever used.
+ if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
+ // Add non-null fake IR, e.g.,
+ // ...x
+ // invoke(rcv, ...)
+ // goto A
+ // ...
+ // A: non_null_rcv <- non-null(rcv)
+ // ...y
+ Value nonNullValue =
+ code.createValue(
+ knownToBeNonNullValue.getTypeLattice(), knownToBeNonNullValue.getLocalInfo());
+ nonNullValueCollector.add(nonNullValue);
+ NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
+ nonNull.setPosition(current.getPosition());
+ if (blockWithNonNullInstruction != block) {
+ // If we split, add non-null IR on top of the new split block.
+ blockWithNonNullInstruction.listIterator().add(nonNull);
+ } else {
+ // Otherwise, just add it to the current block at the position of the iterator.
+ iterator.add(nonNull);
+ }
+
+ // Replace all users of the original value that are dominated by either the current
+ // block or the new split-off block.
+ knownToBeNonNullValue.replaceSelectiveUsers(
+ nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+ }
}
// Add non-null on top of the successor block if the current block ends with a null check.
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index f45a3de..c47a7bd 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -401,7 +401,7 @@
builder.append(minifiedRange).append(':');
}
builder.append(signature);
- if (originalRange != null) {
+ if (originalRange != null && !minifiedRange.equals(originalRange)) {
builder.append(":").append(originalRange);
}
builder.append(" -> ").append(renamedName);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index de42c18..0be9f91 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -68,6 +68,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -253,7 +254,6 @@
private void enqueueRootItems(Map<DexDefinition, ProguardKeepRule> items) {
items.entrySet().forEach(this::enqueueRootItem);
- pinnedItems.addAll(items.keySet());
}
private void enqueueRootItem(Entry<DexDefinition, ProguardKeepRule> root) {
@@ -261,7 +261,10 @@
}
private void enqueueRootItem(DexDefinition item, ProguardKeepRule rule) {
- KeepReason reason = KeepReason.dueToKeepRule(rule);
+ enqueueRootItem(item, KeepReason.dueToKeepRule(rule));
+ }
+
+ private void enqueueRootItem(DexDefinition item, KeepReason reason) {
if (item.isDexClass()) {
DexClass clazz = item.asDexClass();
workList.add(Action.markInstantiated(clazz, reason));
@@ -285,6 +288,7 @@
} else {
throw new IllegalArgumentException(item.toString());
}
+ pinnedItems.add(item);
}
private void enqueueHolderIfDependentNonStaticMember(
@@ -391,6 +395,10 @@
// Revisit the current method to implicitly add -keep rule for items with reflective access.
pendingReflectiveUses.add(currentMethod);
}
+ // See comment in handleJavaLangEnumValueOf.
+ if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
+ pendingReflectiveUses.add(currentMethod);
+ }
if (!registerItemWithTarget(staticInvokes, method)) {
return false;
}
@@ -1150,6 +1158,25 @@
}
}
+ private DexMethod generatedEnumValuesMethod(DexClass enumClass) {
+ DexType arrayOfEnumClass =
+ appInfo.dexItemFactory.createType(
+ appInfo.dexItemFactory.createString("[" + enumClass.type.toDescriptorString()));
+ DexProto proto = appInfo.dexItemFactory.createProto(arrayOfEnumClass);
+ return appInfo.dexItemFactory.createMethod(
+ enumClass.type, proto, appInfo.dexItemFactory.createString("values"));
+ }
+
+ private void markEnumValuesAsReachable(DexClass clazz, KeepReason reason) {
+ DexEncodedMethod valuesMethod = clazz.lookupMethod(generatedEnumValuesMethod(clazz));
+ if (valuesMethod != null) {
+ // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
+ // marking of not renaming is in the root set.
+ enqueueRootItem(valuesMethod, reason);
+ rootSet.noObfuscation.add(valuesMethod);
+ }
+ }
+
private static void fillWorkList(Deque<DexType> worklist, DexType type) {
if (type.isInterface()) {
// We need to check if the method is shadowed by a class that directly implements
@@ -1534,15 +1561,23 @@
DexType originHolder = method.method.holder;
Origin origin = appInfo.originFor(originHolder);
IRCode code = method.buildIR(appInfo, appView.graphLense(), options, origin);
- code.instructionIterator().forEachRemaining(this::handleReflectiveBehavior);
+ Iterator<Instruction> iterator = code.instructionIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ handleReflectiveBehavior(method, instruction);
+ }
}
- private void handleReflectiveBehavior(Instruction instruction) {
+ private void handleReflectiveBehavior(DexEncodedMethod method, Instruction instruction) {
if (!instruction.isInvokeMethod()) {
return;
}
InvokeMethod invoke = instruction.asInvokeMethod();
DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod == appInfo.dexItemFactory.enumMethods.valueOf) {
+ handleJavaLangEnumValueOf(method, invoke);
+ return;
+ }
if (!isReflectionMethod(appInfo.dexItemFactory, invokedMethod)) {
return;
}
@@ -1577,6 +1612,20 @@
}
}
+ private void handleJavaLangEnumValueOf(DexEncodedMethod method, InvokeMethod invoke) {
+ // The use of java.lang.Enum.valueOf(java.lang.Class, java.lang.String) will indirectly
+ // access the values() method of the enum class passed as the first argument. The method
+ // SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will
+ // call this method.
+ if (invoke.inValues().get(0).isConstClass()) {
+ DexClass clazz =
+ appInfo.definitionFor(invoke.inValues().get(0).definition.asConstClass().getValue());
+ if (clazz.accessFlags.isEnum() && clazz.superType == appInfo.dexItemFactory.enumType) {
+ markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method));
+ }
+ }
+ }
+
private static class Action {
final Kind kind;
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 828a463..e6fd878 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -41,8 +41,7 @@
public enum LineNumberOptimization {
OFF,
- ON,
- IDENTITY_MAPPING
+ ON
}
public final DexItemFactory itemFactory;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 66490b2..e81d920 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -10,6 +10,16 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
+import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
+import com.android.tools.r8.graph.DexDebugEvent.Default;
+import com.android.tools.r8.graph.DexDebugEvent.EndLocal;
+import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
+import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
+import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
+import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
+import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
import com.android.tools.r8.graph.DexDebugEventBuilder;
import com.android.tools.r8.graph.DexDebugEventVisitor;
import com.android.tools.r8.graph.DexDebugInfo;
@@ -41,86 +51,6 @@
public class LineNumberOptimizer {
- // EventFilter is a visitor for DebugEvents, splits events into two sinks:
- // - Forwards non-positional events unchanged into a BypassedEventReceiver
- // - Forwards positional events, accumulated into DexDebugPositionStates, into
- // positionEventReceiver.
- private static class EventFilter implements DexDebugEventVisitor {
- private final BypassedEventReceiver bypassedEventReceiver;
- private final PositionEventReceiver positionEventReceiver;
-
- private interface BypassedEventReceiver {
- void receiveBypassedEvent(DexDebugEvent event);
- }
-
- private interface PositionEventReceiver {
- void receivePositionEvent(DexDebugPositionState positionState);
- }
-
- private final DexDebugPositionState positionState;
-
- private EventFilter(
- int startLine,
- DexMethod method,
- BypassedEventReceiver bypassedEventReceiver,
- PositionEventReceiver positionEventReceiver) {
- positionState = new DexDebugPositionState(startLine, method);
- this.bypassedEventReceiver = bypassedEventReceiver;
- this.positionEventReceiver = positionEventReceiver;
- }
-
- @Override
- public void visit(DexDebugEvent.SetPrologueEnd event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.SetEpilogueBegin event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.StartLocal event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.EndLocal event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.RestartLocal event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.AdvancePC advancePC) {
- positionState.visit(advancePC);
- }
-
- @Override
- public void visit(DexDebugEvent.AdvanceLine advanceLine) {
- positionState.visit(advanceLine);
- }
-
- @Override
- public void visit(DexDebugEvent.SetInlineFrame setInlineFrame) {
- positionState.visit(setInlineFrame);
- }
-
- @Override
- public void visit(DexDebugEvent.Default defaultEvent) {
- positionState.visit(defaultEvent);
- positionEventReceiver.receivePositionEvent(positionState);
- }
-
- @Override
- public void visit(DexDebugEvent.SetFile setFile) {
- positionState.visit(setFile);
- }
- }
-
// PositionRemapper is a stateful function which takes a position (represented by a
// DexDebugPositionState) and returns a remapped Position.
private interface PositionRemapper {
@@ -165,6 +95,11 @@
this.processedEvents = processedEvents;
}
+ private void emitAdvancePc(int pc) {
+ processedEvents.add(new AdvancePC(pc - previousPc));
+ previousPc = pc;
+ }
+
private void emitPositionEvents(int currentPc, Position currentPosition) {
if (previousPosition == null) {
startLine = currentPosition.line;
@@ -255,7 +190,6 @@
for (DexEncodedMethod method : methods) {
List<MappedPosition> mappedPositions = new ArrayList<>();
-
Code code = method.getCode();
if (code != null) {
if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
@@ -469,36 +403,96 @@
DexDebugInfo debugInfo = dexCode.getDebugInfo();
List<DexDebugEvent> processedEvents = new ArrayList<>();
- // Our pipeline will be:
- // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
- // [processedEvents]
PositionEventEmitter positionEventEmitter =
new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
- EventFilter eventFilter =
- new EventFilter(
- debugInfo.startLine,
- method.method,
- processedEvents::add,
- positionState -> {
- int currentLine = positionState.getCurrentLine();
- assert currentLine >= 0;
- Position position =
- positionRemapper.createRemappedPosition(
- positionState.getCurrentLine(),
- positionState.getCurrentFile(),
- positionState.getCurrentMethod(),
- positionState.getCurrentCallerPosition());
- mappedPositions.add(
- new MappedPosition(
- positionState.getCurrentMethod(),
- currentLine,
- positionState.getCurrentCallerPosition(),
- position.line));
- positionEventEmitter.emitPositionEvents(positionState.getCurrentPc(), position);
- });
+ // Debug event visitor to map line numbers.
+ // TODO(117268618): Cleanup the duplicate pc tracking.
+ DexDebugEventVisitor visitor =
+ new DexDebugEventVisitor() {
+ DexDebugPositionState state =
+ new DexDebugPositionState(debugInfo.startLine, method.method);
+ int currentPc = 0;
+
+ private void flushPc() {
+ if (currentPc != state.getCurrentPc()) {
+ positionEventEmitter.emitAdvancePc(state.getCurrentPc());
+ currentPc = state.getCurrentPc();
+ }
+ }
+
+ @Override
+ public void visit(AdvancePC advancePC) {
+ state.visit(advancePC);
+ }
+
+ @Override
+ public void visit(AdvanceLine advanceLine) {
+ state.visit(advanceLine);
+ }
+
+ @Override
+ public void visit(SetInlineFrame setInlineFrame) {
+ state.visit(setInlineFrame);
+ }
+
+ @Override
+ public void visit(Default defaultEvent) {
+ state.visit(defaultEvent);
+ int currentLine = state.getCurrentLine();
+ assert currentLine >= 0;
+ Position position =
+ positionRemapper.createRemappedPosition(
+ state.getCurrentLine(),
+ state.getCurrentFile(),
+ state.getCurrentMethod(),
+ state.getCurrentCallerPosition());
+ mappedPositions.add(
+ new MappedPosition(
+ state.getCurrentMethod(),
+ currentLine,
+ state.getCurrentCallerPosition(),
+ position.line));
+ positionEventEmitter.emitPositionEvents(state.getCurrentPc(), position);
+ currentPc = state.getCurrentPc();
+ }
+
+ @Override
+ public void visit(SetFile setFile) {
+ processedEvents.add(setFile);
+ }
+
+ @Override
+ public void visit(SetPrologueEnd setPrologueEnd) {
+ processedEvents.add(setPrologueEnd);
+ }
+
+ @Override
+ public void visit(SetEpilogueBegin setEpilogueBegin) {
+ processedEvents.add(setEpilogueBegin);
+ }
+
+ @Override
+ public void visit(StartLocal startLocal) {
+ flushPc();
+ processedEvents.add(startLocal);
+ }
+
+ @Override
+ public void visit(EndLocal endLocal) {
+ flushPc();
+ processedEvents.add(endLocal);
+ }
+
+ @Override
+ public void visit(RestartLocal restartLocal) {
+ flushPc();
+ processedEvents.add(restartLocal);
+ }
+ };
+
for (DexDebugEvent event : debugInfo.events) {
- event.accept(eventFilter);
+ event.accept(visitor);
}
DexDebugInfo optimizedDebugInfo =
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 6dde9ed..515d191 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -105,7 +105,11 @@
private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
private static final String PROGUARD = PROGUARD5_2_1;
+ private static final String RETRACE6_0_1 = "third_party/proguard/proguard6.0.1/bin/retrace";
+ private static final String RETRACE = RETRACE6_0_1;
+
public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
+ public static final Path R8_LIB_JAR = Paths.get(LIBS_DIR, "r8lib_with_deps.jar");
public enum DexVm {
ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
@@ -524,6 +528,13 @@
return PROGUARD6_0_1 + ".sh";
}
+ private static String getRetraceScript() {
+ if (isWindows()) {
+ return RETRACE + ".bat";
+ }
+ return RETRACE + ".sh";
+ }
+
private static Path getDxExecutablePath() {
String toolsDir = toolsDir();
String executableName = toolsDir.equals("windows") ? "dx.bat" : "dx";
@@ -1498,6 +1509,23 @@
return runProguard(getProguard6Script(), inJar, outJar, configs, map);
}
+ public static ProcessResult runRetraceRaw(Path map, Path stackTrace) throws IOException {
+ List<String> command = new ArrayList<>();
+ command.add(getRetraceScript());
+ command.add(map.toString());
+ command.add(stackTrace.toString());
+ ProcessBuilder builder = new ProcessBuilder(command);
+ return ToolHelper.runProcess(builder);
+ }
+
+ public static String runRetrace(Path map, Path stackTrace) throws IOException {
+ ProcessResult result = runRetraceRaw(map, stackTrace);
+ if (result.exitCode != 0) {
+ fail("Retrace failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+ }
+ return result.stdout;
+ }
+
public static class ProcessResult {
public final int exitCode;
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
index 02df3d1..7202914 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -35,6 +35,10 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+/**
+ * This test relies on a freshly built from builds/libs/r8lib_with_deps.jar. If this test fails
+ * rebuild r8lib_with_deps by calling test.py or gradle r8libWithdeps.
+ */
public class BootstrapCurrentEqualityTest extends TestBase {
private static final String R8_NAME = "com.android.tools.r8.R8";
@@ -45,7 +49,7 @@
"-keep class " + HELLO_NAME + " {", " public static void main(...);", "}",
};
- private class R8Result {
+ private static class R8Result {
final ProcessResult processResult;
final Path outputJar;
@@ -70,18 +74,19 @@
@BeforeClass
public static void beforeAll() throws Exception {
- r8R8Debug = compileR8(CompilationMode.DEBUG);
- r8R8Release = compileR8(CompilationMode.RELEASE);
+ r8R8Debug = compileR8("--debug");
+ r8R8Release = compileR8("--release");
}
- private static Path compileR8(CompilationMode mode) throws Exception {
+ private static Path compileR8(String mode) throws Exception {
// Run R8 on r8.jar.
- Path output = runR8(ToolHelper.R8_JAR, testFolder.newFolder().toPath(), mode);
+ R8Result output = runExternalR8(
+ ToolHelper.R8_LIB_JAR, ToolHelper.R8_LIB_JAR, testFolder.newFolder().toPath(), MAIN_KEEP, mode);
// Check that all non-abstract classes in the R8'd R8 implement all abstract/interface methods
// from their supertypes. This is a sanity check for the tree shaking and minification.
- AndroidApp app = AndroidApp.builder().addProgramFile(output).build();
+ AndroidApp app = AndroidApp.builder().addProgramFile(output.outputJar).build();
new ClassHierarchyVerifier(new CodeInspector(app)).run();
- return output;
+ return output.outputJar;
}
@Test
@@ -95,10 +100,10 @@
private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
throws Exception {
R8Result runR8Debug =
- runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--debug");
+ runExternalR8(ToolHelper.R8_LIB_JAR, program, temp.newFolder().toPath(), keep, "--debug");
assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar, args).toString());
R8Result runR8Release =
- runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--release");
+ runExternalR8(ToolHelper.R8_LIB_JAR, program, temp.newFolder().toPath(), keep, "--release");
assertEquals(runResult.toString(), ToolHelper.runJava(runR8Release.outputJar, args).toString());
RunR8AndCheck(r8R8Debug, program, runR8Debug, keep, "--debug");
RunR8AndCheck(r8R8Debug, program, runR8Release, keep, "--release");
@@ -115,25 +120,17 @@
assertProgramsEqual(result.outputJar, runR8R8.outputJar);
}
- private static Path runR8(Path inputJar, Path outputPath, CompilationMode mode) throws Exception {
- Path outputJar = outputPath.resolve("output.jar");
- ToolHelper.runR8(
- R8Command.builder()
- .setMode(mode)
- .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
- .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
- .addProgramFiles(inputJar)
- .addProguardConfigurationFiles(MAIN_KEEP)
- .build());
- return outputJar;
- }
-
- private R8Result runExternalR8(
+ private static R8Result runExternalR8(
Path r8Jar, Path inputJar, Path output, String[] keepRules, String mode) throws Exception {
Path pgConfigFile = output.resolve("keep.rules");
+ FileUtils.writeTextFile(pgConfigFile, keepRules);
+ return runExternalR8(r8Jar, inputJar, output, pgConfigFile, mode);
+ }
+
+ private static R8Result runExternalR8(
+ Path r8Jar, Path inputJar, Path output, Path keepRules, String mode) throws Exception {
Path outputJar = output.resolve("output.jar");
Path pgMapFile = output.resolve("map.txt");
- FileUtils.writeTextFile(pgConfigFile, keepRules);
ProcessResult processResult =
ToolHelper.runJava(
r8Jar,
@@ -145,7 +142,7 @@
"--output",
outputJar.toString(),
"--pg-conf",
- pgConfigFile.toString(),
+ keepRules.toString(),
mode,
"--pg-map-output",
pgMapFile.toString());
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index 616256e..78e9a56 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -96,12 +96,6 @@
}
@Test
- public void testIdentityCompilation() throws Throwable {
- // Compilation will fail if the identity translation does.
- makeConfig(LineNumberOptimization.IDENTITY_MAPPING, true, false, runtimeKind);
- }
-
- @Test
public void testNotOptimized() throws Throwable {
testRelease(
makeConfig(LineNumberOptimization.OFF, false, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 564a81c..e569ad2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -69,6 +69,8 @@
|| NonNullTracker.throwsOnNullInput(prev)
|| (prev.isIf() && prev.asIf().isZeroTest())
|| !curr.getBlock().getPredecessors().contains(prev.getBlock()));
+ // Make sure non-null is used.
+ assertTrue(curr.outValue().numberOfAllUsers() > 0);
count++;
}
}
@@ -114,7 +116,7 @@
buildAndTest(NonNullAfterInvoke.class, foo, 1, this::checkInvokeGetsNonNullReceiver);
MethodSignature bar =
new MethodSignature("bar", "int", new String[]{"java.lang.String"});
- buildAndTest(NonNullAfterInvoke.class, bar, 2, this::checkInvokeGetsNullReceiver);
+ buildAndTest(NonNullAfterInvoke.class, bar, 1, this::checkInvokeGetsNullReceiver);
}
@Test
@@ -176,6 +178,6 @@
buildAndTest(NonNullAfterNullCheck.class, bar, 1, this::checkInvokeGetsNonNullReceiver);
MethodSignature baz =
new MethodSignature("baz", "int", new String[]{"java.lang.String"});
- buildAndTest(NonNullAfterNullCheck.class, baz, 2, this::checkInvokeGetsNullReceiver);
+ buildAndTest(NonNullAfterNullCheck.class, baz, 1, this::checkInvokeGetsNullReceiver);
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
index a8708eb..66cd45e 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinification.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -4,17 +4,19 @@
package com.android.tools.r8.naming;
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collection;
@@ -22,6 +24,12 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
+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;
+import org.objectweb.asm.Type;
@RunWith(Parameterized.class)
public class EnumMinification extends TestBase {
@@ -37,29 +45,57 @@
this.backend = backend;
}
+ private AndroidApp buildApp(Class<?> mainClass, byte[] enumClassFile) throws Exception {
+ return ToolHelper.runR8(
+ R8Command.builder()
+ .addClassProgramData(ToolHelper.getClassAsBytes(mainClass), Origin.unknown())
+ .addClassProgramData(enumClassFile, Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(keepMainProguardConfiguration(mainClass)), Origin.unknown())
+ .setProgramConsumer(emptyConsumer(backend))
+ .build());
+ }
+
+ public void runTest(
+ Class<?> mainClass, byte[] enumClass, String enumTypeName, boolean valueOfKept)
+ throws Exception {
+ AndroidApp output = buildApp(mainClass, enumClass);
+
+ CodeInspector inspector = new CodeInspector(output);
+ ClassSubject clazz = inspector.clazz(enumTypeName);
+ // The class and fields - including field $VALUES and method valueOf - can be renamed. Only
+ // the values() method needs to be
+ assertThat(clazz, isRenamed());
+ assertThat(clazz.field(enumTypeName, "VALUE1"), isRenamed());
+ assertThat(clazz.field(enumTypeName, "VALUE2"), isRenamed());
+ assertThat(clazz.field(enumTypeName + "[]", "$VALUES"), isRenamed());
+ assertThat(
+ clazz.method(enumTypeName, "valueOf", ImmutableList.of("java.lang.String")),
+ valueOfKept ? isRenamed() : not(isPresent()));
+ assertThat(clazz.method(enumTypeName + "[]", "values", ImmutableList.of()), not(isRenamed()));
+
+ assertEquals("VALUE1", runOnVM(output, mainClass, backend));
+ }
+
@Test
public void test() throws Exception {
- AndroidApp output =
- ToolHelper.runR8(
- R8Command.builder()
- .addClassProgramData(ToolHelper.getClassAsBytes(Main.class), Origin.unknown())
- .addClassProgramData(ToolHelper.getClassAsBytes(Enum.class), Origin.unknown())
- .addProguardConfiguration(
- ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
- .setProgramConsumer(emptyConsumer(backend))
- .build());
+ runTest(Main.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), true);
+ }
- // TODO(117299356): valueOf on enum fails for minified enums.
- ProcessResult result = runOnVMRaw(output, Main.class, backend);
- assertEquals(1, result.exitCode);
- assertThat(
- result.stderr,
- containsString(
- backend == Backend.DEX
- ? ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4_HOST)
- ? "java.lang.NoSuchMethodException"
- : "java.lang.NullPointerException"
- : "java.lang.IllegalArgumentException"));
+ @Test
+ public void testAsmDump() throws Exception {
+ runTest(Main.class, EnumDump.dump(true), "com.android.tools.r8.naming.Enum", true);
+ }
+
+ @Test
+ public void testWithoutValuesMethod() throws Exception {
+ // This should not fail even if the values method is not present.
+ buildApp(Main.class, EnumDump.dump(false));
+ }
+
+ @Test
+ public void testJavaLangEnumValueOf() throws Exception {
+ runTest(Main2.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), false);
}
}
@@ -67,7 +103,7 @@
public static void main(String[] args) {
Enum e = Enum.valueOf("VALUE1");
- System.out.println(e);
+ System.out.print(e);
}
}
@@ -75,3 +111,219 @@
VALUE1,
VALUE2
}
+
+class Main2 {
+ public static void main(String[] args) {
+ // Use java.lang.Enum.valueOf instead of com.android.tools.r8.naming.Enum.valueOf.
+ System.out.print(java.lang.Enum.valueOf(Enum.class, "VALUE1"));
+ }
+}
+/* Dump of javac generated code from the following enum class (the one just above):
+ *
+ * package com.android.tools.r8.naming;
+ *
+ * enum Enum {
+ * VALUE1,
+ * VALUE2
+ * }
+ *
+ */
+class EnumDump implements Opcodes {
+
+ public static byte[] dump(boolean includeValuesMethod) {
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_FINAL | ACC_SUPER | ACC_ENUM,
+ "com/android/tools/r8/naming/Enum",
+ "Ljava/lang/Enum<Lcom/android/tools/r8/naming/Enum;>;",
+ "java/lang/Enum",
+ null);
+
+ classWriter.visitSource("EnumMinification.java", null);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC | ACC_ENUM,
+ "VALUE1",
+ "Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC | ACC_ENUM,
+ "VALUE2",
+ "Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PRIVATE | ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC,
+ "$VALUES",
+ "[Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ fieldVisitor.visitEnd();
+ }
+ if (includeValuesMethod) {
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "values",
+ "()[Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(72, label0);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "$VALUES",
+ "[Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "[Lcom/android/tools/r8/naming/Enum;",
+ "clone",
+ "()Ljava/lang/Object;",
+ false);
+ methodVisitor.visitTypeInsn(CHECKCAST, "[Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitMaxs(1, 0);
+ methodVisitor.visitEnd();
+ }
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "valueOf",
+ "(Ljava/lang/String;)Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(72, label0);
+ methodVisitor.visitLdcInsn(Type.getType("Lcom/android/tools/r8/naming/Enum;"));
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/lang/Enum",
+ "valueOf",
+ "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;",
+ false);
+ methodVisitor.visitTypeInsn(CHECKCAST, "com/android/tools/r8/naming/Enum");
+ methodVisitor.visitInsn(ARETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", "()V", null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(72, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitVarInsn(ILOAD, 2);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/Enum", "<init>", "(Ljava/lang/String;I)V", false);
+ methodVisitor.visitInsn(RETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable(
+ "this", "Lcom/android/tools/r8/naming/Enum;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(3, 3);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(73, label0);
+ methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/naming/Enum");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitLdcInsn("VALUE1");
+ methodVisitor.visitInsn(ICONST_0);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/naming/Enum",
+ "<init>",
+ "(Ljava/lang/String;I)V",
+ false);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "VALUE1",
+ "Lcom/android/tools/r8/naming/Enum;");
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(74, label1);
+ methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/naming/Enum");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitLdcInsn("VALUE2");
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/naming/Enum",
+ "<init>",
+ "(Ljava/lang/String;I)V",
+ false);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "VALUE2",
+ "Lcom/android/tools/r8/naming/Enum;");
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(72, label2);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitTypeInsn(ANEWARRAY, "com/android/tools/r8/naming/Enum");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitInsn(ICONST_0);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "VALUE1",
+ "Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitInsn(AASTORE);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "VALUE2",
+ "Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitInsn(AASTORE);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "$VALUES",
+ "[Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(4, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java
new file mode 100644
index 0000000..e1c61c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java
@@ -0,0 +1,166 @@
+// 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.naming.retrace;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+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;
+
+@RunWith(Parameterized.class)
+public class RetraceTest extends TestBase {
+ private Backend backend;
+ private CompilationMode mode;
+
+ @Parameters(name = "Backend: {0}, mode: {1}")
+ public static Collection<Object[]> data() {
+ List<Object[]> parameters = new ArrayList<>();
+ for (Backend backend : Backend.values()) {
+ for (CompilationMode mode : CompilationMode.values()) {
+ parameters.add(new Object[] {backend, mode});
+ }
+ }
+ return parameters;
+ }
+
+ public RetraceTest(Backend backend, CompilationMode mode) {
+ this.backend = backend;
+ this.mode = mode;
+ }
+
+ private List<String> retrace(String map, List<String> stackTrace) throws IOException {
+ Path t = temp.newFolder().toPath();
+ Path mapFile = t.resolve("map");
+ Path stackTraceFile = t.resolve("stackTrace");
+ FileUtils.writeTextFile(mapFile, map);
+ FileUtils.writeTextFile(stackTraceFile, stackTrace);
+ return StringUtils.splitLines(ToolHelper.runRetrace(mapFile, stackTraceFile));
+ }
+
+ private boolean isDalvik() {
+ return backend == Backend.DEX && ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST);
+ }
+
+ private List<String> extractStackTrace(ProcessResult result) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ List<String> stderr = StringUtils.splitLines(result.stderr);
+ Iterator<String> iterator = stderr.iterator();
+
+ // A Dalvik stacktrace looks like this:
+ // W(209693) threadid=1: thread exiting with uncaught exception (group=0xf616cb20) (dalvikvm)
+ // java.lang.NullPointerException
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:133)
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:139)
+ // \tat com.android.tools.r8.naming.retrace.Main.main(:145)
+ // \tat dalvik.system.NativeStart.main(Native Method)
+ //
+ // An Art 5.1.1 and 6.0.1 stacktrace looks like this:
+ // java.lang.NullPointerException: throw with null exception
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:154)
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:160)
+ // \tat com.android.tools.r8.naming.retrace.Main.main(:166)
+ //
+ // An Art 7.0.0 and latest stacktrace looks like this:
+ // Exception in thread "main" java.lang.NullPointerException: throw with null exception
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:150)
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:156)
+ // \tat com.android.tools.r8.naming.retrace.Main.main(:162)
+ int last = stderr.size();
+ if (isDalvik()) {
+ // Skip the bottom frame "dalvik.system.NativeStart.main".
+ last--;
+ }
+ // Take all lines from the bottom starting with "\tat ".
+ int first = last;
+ while (first - 1 >= 0 && stderr.get(first - 1).startsWith("\tat ")) {
+ first--;
+ }
+ for (int i = first; i < last; i++) {
+ builder.add(stderr.get(i));
+ }
+ return builder.build();
+ }
+
+ public void runTest(Class<?> mainClass, BiConsumer<List<String>, List<String>> checker)
+ throws Exception {
+ StringBuilder proguardMapBuilder = new StringBuilder();
+ AndroidApp output =
+ ToolHelper.runR8(
+ R8Command.builder()
+ .setMode(mode)
+ .addClassProgramData(ToolHelper.getClassAsBytes(mainClass), Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(keepMainProguardConfiguration(mainClass)), Origin.unknown())
+ .setProgramConsumer(emptyConsumer(backend))
+ .setProguardMapConsumer((string, ignore) -> proguardMapBuilder.append(string))
+ .build());
+
+ ProcessResult result = runOnVMRaw(output, mainClass, backend);
+ List<String> stackTrace = extractStackTrace(result);
+ List<String> retracesStackTrace = retrace(proguardMapBuilder.toString(), stackTrace);
+ checker.accept(stackTrace, retracesStackTrace);
+ }
+
+ @Test
+ public void test() throws Exception {
+ runTest(
+ Main.class,
+ (List<String> stackTrace, List<String> retracesStackTrace) -> {
+ assertEquals(
+ mode == CompilationMode.RELEASE, stackTrace.size() != retracesStackTrace.size());
+ if (mode == CompilationMode.DEBUG) {
+ assertThat(stackTrace.get(0), not(containsString("method2")));
+ assertThat(stackTrace.get(1), not(containsString("method1")));
+ assertThat(stackTrace.get(2), containsString("main"));
+ }
+ assertEquals(3, retracesStackTrace.size());
+ assertThat(retracesStackTrace.get(0), containsString("method2"));
+ assertThat(retracesStackTrace.get(1), containsString("method1"));
+ assertThat(retracesStackTrace.get(2), containsString("main"));
+ });
+ }
+}
+
+class Main {
+ public static void method2(int i) {
+ System.out.println("In method2");
+ throw null;
+ }
+
+ public static void method1(String s) {
+ System.out.println("In method1");
+ for (int i = 0; i < 10; i++) {
+ method2(Integer.parseInt(s));
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println("In main");
+ method1("1");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
index bdd8e1a..51c2c35 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.graph.invokesuper.Consumer;
@@ -103,8 +102,7 @@
ClassSubject cls = inspector.clazz(clazz);
assertThat(cls, isPresent());
assertEquals(1, cls.asFoundClassSubject().allFields().size());
- // TODD(116079696): This is a hack!
- cls.forAllFields(field -> assertNotEquals(1, field.getFinalName().length()));
+ cls.forAllFields(field -> assertThat(field, not(isRenamed())));
}
}
@@ -115,8 +113,7 @@
assertThat(cls, isPresent());
assertThat(cls, isRenamed());
assertEquals(1, cls.asFoundClassSubject().allFields().size());
- // TODD(116079696): This is a hack!
- cls.forAllFields(field -> assertEquals(1, field.getFinalName().length()));
+ cls.forAllFields(field -> assertThat(field, isRenamed()));
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index bc31615..28404f0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -39,6 +39,7 @@
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
@@ -136,6 +137,23 @@
return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(string));
}
+ String mapType(Map<String, String> mapping, String typeName) {
+ final String ARRAY_POSTFIX = "[]";
+ int arrayCount = 0;
+ while (typeName.endsWith(ARRAY_POSTFIX)) {
+ arrayCount++;
+ typeName = typeName.substring(0, typeName.length() - 2);
+ }
+ String mappedType = mapping.get(typeName);
+ if (mappedType == null) {
+ return null;
+ }
+ for (int i = 0; i < arrayCount; i++) {
+ mappedType += ARRAY_POSTFIX;
+ }
+ return mappedType;
+ }
+
static <S, T extends Subject> void forAll(
S[] items,
BiFunction<S, FoundClassSubject, ? extends T> constructor,
@@ -248,12 +266,11 @@
}
String getObfuscatedTypeName(String originalTypeName) {
- String obfuscatedType = null;
+ String obfuscatedTypeName = null;
if (mapping != null) {
- obfuscatedType = originalToObfuscatedMapping.get(originalTypeName);
+ obfuscatedTypeName = mapType(originalToObfuscatedMapping, originalTypeName);
}
- obfuscatedType = obfuscatedType == null ? originalTypeName : obfuscatedType;
- return obfuscatedType;
+ return obfuscatedTypeName != null ? obfuscatedTypeName : originalTypeName;
}
InstructionSubject createInstructionSubject(Instruction instruction) {
@@ -321,7 +338,7 @@
public String parsedTypeName(String name) {
String type = name;
if (originalToObfuscatedMapping != null) {
- String original = originalToObfuscatedMapping.inverse().get(name);
+ String original = mapType(originalToObfuscatedMapping.inverse(), name);
type = original != null ? original : name;
}
signature.append(type);
@@ -330,14 +347,17 @@
@Override
public String parsedInnerTypeName(String enclosingType, String name) {
- String type;
+ String type = null;
if (originalToObfuscatedMapping != null) {
// The enclosingType has already been mapped if a mapping is present.
String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
- type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
- if (type != null) {
- assert type.startsWith(enclosingType + "$");
- name = type.substring(enclosingType.length() + 1);
+ if (minifiedEnclosing != null) {
+ assert !minifiedEnclosing.contains("[");
+ type = mapType(originalToObfuscatedMapping.inverse(), minifiedEnclosing + "$" + name);
+ if (type != null) {
+ assert type.startsWith(enclosingType + "$");
+ name = type.substring(enclosingType.length() + 1);
+ }
}
} else {
type = enclosingType + "$" + name;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index ae42b14..e65a8b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -67,9 +67,9 @@
//
// whereas the final signature is for X.a is "a a"
String obfuscatedType = signature.type;
- String originalType = codeInspector.originalToObfuscatedMapping.inverse().get(obfuscatedType);
+ String originalType =
+ codeInspector.mapType(codeInspector.originalToObfuscatedMapping.inverse(), obfuscatedType);
String fieldType = originalType != null ? originalType : obfuscatedType;
-
FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
diff --git a/tools/test.py b/tools/test.py
index b57d00f..72e7e01 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -156,6 +156,8 @@
gradle_args.append('-PHEAD_sha1=' + utils.get_HEAD_sha1())
# Add Gradle tasks
gradle_args.append('cleanTest')
+ # Build R8lib with dependencies for bootstrapping tests.
+ gradle_args.append('r8libWithDeps')
gradle_args.append('test')
# Test filtering. Must always follow the 'test' task.
for testFilter in args: