Merge "Build 'kotlin' prefix to not be relocated."
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55c4134..0ef8a34 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -489,17 +489,22 @@
}
public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
+ DexClass clazz = definitionFor(type);
+ assert clazz != null;
+ return canTriggerStaticInitializer(clazz, ignoreTypeItself);
+ }
+
+ public boolean canTriggerStaticInitializer(DexClass clazz, boolean ignoreTypeItself) {
Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
// Process superclass chain.
- DexType clazz = type;
- while (clazz != null && clazz != dexItemFactory.objectType) {
- DexClass definition = definitionFor(clazz);
- if (canTriggerStaticInitializer(definition) && (!ignoreTypeItself || clazz != type)) {
+ DexClass current = clazz;
+ while (current != null && current.type != dexItemFactory.objectType) {
+ if (canTriggerStaticInitializer(current) && (!ignoreTypeItself || current != clazz)) {
return true;
}
- knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
- clazz = definition.superType;
+ knownInterfaces.addAll(Arrays.asList(current.interfaces.values));
+ current = current.superType != null ? definitionFor(current.superType) : null;
}
// Process interfaces.
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 b8e0bd8..776cc42 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import java.util.Collections;
import java.util.List;
import org.objectweb.asm.Label;
@@ -230,7 +231,13 @@
throw new Unimplemented(
"Converting CfCode to IR not supported for DEX output of synchronized methods.");
}
- CfSourceCode source = new CfSourceCode(this, encodedMethod, callerPosition, origin);
+ CfSourceCode source =
+ new CfSourceCode(
+ this,
+ encodedMethod,
+ callerPosition,
+ origin,
+ options.lineNumberOptimization == LineNumberOptimization.ON);
IRBuilder builder =
(generator == null)
? new IRBuilder(encodedMethod, appInfo, source, options)
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 64c85f3..fc1ef7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.ThrowingConsumer;
import com.google.common.base.MoreObjects;
import com.google.common.collect.Iterators;
import java.util.Arrays;
@@ -138,16 +137,6 @@
}
}
- public <E extends Throwable> void forEachMethodThrowing(
- ThrowingConsumer<DexEncodedMethod, E> consumer) throws E {
- for (DexEncodedMethod method : directMethods()) {
- consumer.accept(method);
- }
- for (DexEncodedMethod method : virtualMethods()) {
- consumer.accept(method);
- }
- }
-
public DexEncodedMethod[] allMethodsSorted() {
int vLen = virtualMethods.length;
int dLen = directMethods.length;
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 8ab2c1a..5bed061 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
@@ -6,7 +6,6 @@
import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
import com.android.tools.r8.graph.DexItemFactory;
@@ -468,7 +467,7 @@
return instruction;
}
}
- throw new Unreachable();
+ return null;
}
public void clearUserInfo() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
new file mode 100644
index 0000000..66456cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -0,0 +1,76 @@
+// 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.ir.code;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Maintains a set of canonical positions. Also supports appending a new caller at the end of the
+ * caller chain of a Position.
+ */
+public class CanonicalPositions {
+ private final Position callerPosition;
+ private final boolean preserveCaller;
+ private final Map<Position, Position> canonicalPositions;
+ private final Position preamblePosition;
+
+ /** For callerPosition and preserveCaller see canonicalizeCallerPosition. */
+ public CanonicalPositions(
+ Position callerPosition,
+ boolean preserveCaller,
+ int expectedPositionsCount,
+ DexMethod method) {
+ canonicalPositions =
+ new HashMap<>(1 + (callerPosition == null ? 0 : 1) + expectedPositionsCount);
+ this.preserveCaller = preserveCaller;
+ this.callerPosition = callerPosition;
+ if (callerPosition != null) {
+ canonicalPositions.put(callerPosition, callerPosition);
+ }
+ preamblePosition =
+ callerPosition == null
+ ? Position.synthetic(0, method, null)
+ : new Position(0, null, method, callerPosition);
+ canonicalPositions.put(preamblePosition, preamblePosition);
+ }
+
+ public Position getPreamblePosition() {
+ return preamblePosition;
+ }
+
+ /**
+ * Update the internal set if this is the first occurence of the position's value and return
+ * canonical instance of position.
+ */
+ public Position getCanonical(Position position) {
+ Position canonical = canonicalPositions.putIfAbsent(position, position);
+ return canonical != null ? canonical : position;
+ }
+
+ /**
+ * Append callerPosition (supplied in constructor) to the end of caller's caller chain and return
+ * the canonical instance. Always returns null if preserveCaller (also supplied in constructor) is
+ * false.
+ */
+ public Position canonicalizeCallerPosition(Position caller) {
+ if (!preserveCaller) {
+ return null;
+ }
+
+ if (caller == null) {
+ return callerPosition;
+ }
+ if (caller.callerPosition == null && callerPosition == null) {
+ return getCanonical(caller);
+ }
+ Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
+ return getCanonical(
+ caller.isNone()
+ ? Position.noneWithMethod(caller.method, callerOfCaller)
+ : new Position(caller.line, caller.file, caller.method, callerOfCaller));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 4cbebcf..92645c4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.CanonicalPositions;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
@@ -184,8 +185,6 @@
private final Position callerPosition;
private final Origin origin;
- // Synthetic position with line = 0.
- private final Position preamblePosition;
private final Reference2IntMap<CfLabel> labelOffsets = new Reference2IntOpenHashMap<>();
private TryHandlerList cachedTryHandlerList;
private LocalVariableList cachedLocalVariableList;
@@ -196,21 +195,31 @@
private Int2ReferenceMap<Int2ObjectMap<DebugLocalInfo>> definitelyLiveIncomingLocals =
new Int2ReferenceOpenHashMap<>();
private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
+ private final CanonicalPositions canonicalPositions;
public CfSourceCode(
- CfCode code, DexEncodedMethod method, Position callerPosition, Origin origin) {
+ CfCode code,
+ DexEncodedMethod method,
+ Position callerPosition,
+ Origin origin,
+ boolean preserveCaller) {
this.code = code;
this.method = method;
this.callerPosition = callerPosition;
this.origin = origin;
- preamblePosition = Position.synthetic(0, method.method, null);
+ int cfPositionCount = 0;
for (int i = 0; i < code.getInstructions().size(); i++) {
CfInstruction instruction = code.getInstructions().get(i);
if (instruction instanceof CfLabel) {
labelOffsets.put((CfLabel) instruction, instructionOffset(i));
}
+ if (instruction instanceof CfPosition) {
+ ++cfPositionCount;
+ }
}
this.state = new CfState(origin);
+ canonicalPositions =
+ new CanonicalPositions(callerPosition, preserveCaller, cfPositionCount, this.method.method);
}
@Override
@@ -383,7 +392,21 @@
}
state.clear();
} else {
- instruction.buildIR(builder, state, this);
+ if (instruction instanceof CfPosition) {
+ CfPosition cfPosition = (CfPosition) instruction;
+ Position position = cfPosition.getPosition();
+ Position newPosition =
+ canonicalPositions.getCanonical(
+ new Position(
+ position.line,
+ position.file,
+ position.method,
+ canonicalPositions.canonicalizeCallerPosition(position.callerPosition)));
+ CfPosition newCfPosition = new CfPosition(cfPosition.getLabel(), newPosition);
+ newCfPosition.buildIR(builder, state, this);
+ } else {
+ instruction.buildIR(builder, state, this);
+ }
ensureDebugValueLiveness(builder);
if (builder.getCFG().containsKey(currentInstructionIndex + 1)) {
recordStateForTarget(currentInstructionIndex + 1, state.getSnapshot());
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 b59794b..9a9b836 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
@@ -40,6 +40,7 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.CanonicalPositions;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
@@ -68,25 +69,20 @@
private Instruction currentDexInstruction = null;
private Position currentPosition = null;
- private Map<Position, Position> canonicalPositions = null;
+ private final CanonicalPositions canonicalPositions;
private final List<ValueType> argumentTypes;
private List<DexDebugEntry> debugEntries = null;
// In case of inlining the position of the invoke in the caller.
- private final Position callerPosition;
private final DexMethod method;
- private final boolean preserveCaller;
- private final Position preamblePosition;
public DexSourceCode(
DexCode code, DexEncodedMethod method, Position callerPosition, boolean preserveCaller) {
this.code = code;
this.proto = method.method.proto;
this.accessFlags = method.accessFlags;
- this.callerPosition = callerPosition;
this.method = method.method;
- this.preserveCaller = preserveCaller;
argumentTypes = computeArgumentTypes();
DexDebugInfo info = code.getDebugInfo();
@@ -94,18 +90,11 @@
debugEntries = info.computeEntries(method.method);
}
canonicalPositions =
- new HashMap<>(
- 1
- + (callerPosition == null ? 0 : 1)
- + (debugEntries == null ? 0 : debugEntries.size()));
- if (callerPosition != null) {
- canonicalPositions.put(callerPosition, callerPosition);
- }
- preamblePosition =
- callerPosition == null
- ? Position.synthetic(0, this.method, null)
- : new Position(0, null, this.method, callerPosition);
- canonicalPositions.put(preamblePosition, preamblePosition);
+ new CanonicalPositions(
+ callerPosition,
+ preserveCaller,
+ debugEntries == null ? 0 : debugEntries.size(),
+ this.method);
}
@Override
@@ -148,7 +137,7 @@
@Override
public void buildPrelude(IRBuilder builder) {
- currentPosition = preamblePosition;
+ currentPosition = canonicalPositions.getPreamblePosition();
if (code.incomingRegisterSize == 0) {
return;
}
@@ -197,7 +186,9 @@
@Override
public Position getDebugPositionAtOffset(int offset) {
DexDebugEntry entry = getDebugEntryAtOffset(offset);
- return entry == null ? preamblePosition : getCanonicalPositionAppendCaller(entry);
+ return entry == null
+ ? canonicalPositions.getPreamblePosition()
+ : getCanonicalPositionAppendCaller(entry);
}
@Override
@@ -250,7 +241,7 @@
int offset = instructionOffset(instructionIndex);
DexDebugEntry entry = getDebugEntryAtOffset(offset);
if (entry == null) {
- currentPosition = preamblePosition;
+ currentPosition = canonicalPositions.getPreamblePosition();
} else {
currentPosition = getCanonicalPositionAppendCaller(entry);
if (entry.lineEntry && entry.address == offset) {
@@ -259,39 +250,17 @@
}
}
- private Position getCanonicalPosition(Position position) {
- Position canonical = canonicalPositions.putIfAbsent(position, position);
- return canonical != null ? canonical : position;
- }
-
- private Position canonicalizeCallerPosition(Position caller) {
- // We are not supposed to get here from getCanonicalPositionAppendCaller if !preserveCaller.
- assert preserveCaller;
-
- if (caller == null) {
- return callerPosition;
- }
- if (caller.callerPosition == null && callerPosition == null) {
- return getCanonicalPosition(caller);
- }
- Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
- return getCanonicalPosition(
- caller.isNone()
- ? Position.noneWithMethod(caller.method, callerOfCaller)
- : new Position(caller.line, caller.file, caller.method, callerOfCaller));
- }
-
private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
// If this instruction has already been inlined then this.method must be the outermost caller.
assert entry.callerPosition == null
|| entry.callerPosition.getOutermostCaller().method == method;
- return getCanonicalPosition(
+ return canonicalPositions.getCanonical(
new Position(
entry.line,
entry.sourceFile,
entry.method,
- preserveCaller ? canonicalizeCallerPosition(entry.callerPosition) : null));
+ canonicalPositions.canonicalizeCallerPosition(entry.callerPosition)));
}
@Override
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 63843ee..3ea97e4 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
@@ -46,6 +46,7 @@
import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -104,6 +105,8 @@
private final Devirtualizer devirtualizer;
private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
+ private final boolean enableWholeProgramOptimizations;
+
private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
private DexString highestSortingString;
@@ -134,6 +137,7 @@
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
: null;
+ this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
this.nonNullTracker = new NonNullTracker();
@@ -172,7 +176,8 @@
}
this.classInliner =
(options.enableClassInlining && options.enableInlining && inliner != null)
- ? new ClassInliner(appInfo.dexItemFactory, options.classInliningInstructionLimit)
+ ? new ClassInliner(
+ appInfo.dexItemFactory, lambdaRewriter, options.classInliningInstructionLimit)
: null;
}
@@ -352,12 +357,7 @@
ExecutorService executor) throws ExecutionException {
List<Future<?>> futures = new ArrayList<>();
for (DexProgramClass clazz : classes) {
- futures.add(
- executor.submit(
- () -> {
- clazz.forEachMethodThrowing(this::convertMethodToDex);
- return null; // we want a Callable not a Runnable to be able to throw
- }));
+ futures.add(executor.submit(() -> clazz.forEachMethod(this::convertMethodToDex)));
}
ThreadUtils.awaitFutures(futures);
}
@@ -561,7 +561,7 @@
try {
codeRewriter.enterCachedClass(clazz);
// Process the generated class, but don't apply any outlining.
- clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
+ clazz.forEachMethod(this::optimizeSynthesizedMethod);
} finally {
codeRewriter.leaveCachedClass(clazz);
}
@@ -695,6 +695,7 @@
codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
codeRewriter.simplifyIf(code, typeEnvironment);
+ new RedundantFieldLoadElimination(appInfo, code, enableWholeProgramOptimizations).run();
if (options.testing.invertConditionals) {
invertConditionalsForTesting(code);
@@ -728,11 +729,6 @@
assert code.isConsistentSSA();
}
- if (interfaceMethodRewriter != null) {
- interfaceMethodRewriter.rewriteMethodReferences(method, code);
- assert code.isConsistentSSA();
- }
-
if (classInliner != null) {
// Class inliner should work before lambda merger, so if it inlines the
// lambda, it is not get collected by merger.
@@ -750,6 +746,11 @@
assert code.isConsistentSSA();
}
+ if (interfaceMethodRewriter != null) {
+ interfaceMethodRewriter.rewriteMethodReferences(method, code);
+ assert code.isConsistentSSA();
+ }
+
if (lambdaMerger != null) {
lambdaMerger.processMethodCode(method, code);
assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 9a88674..1877053 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
+import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -36,11 +38,11 @@
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
-import java.util.stream.Collectors;
public class LensCodeRewriter {
@@ -80,16 +82,7 @@
callSite.methodProto, graphLense::lookupType, protoFixupCache);
DexMethodHandle newBootstrapMethod =
rewriteDexMethodHandle(callSite.bootstrapMethod, method);
- List<DexValue> newArgs = callSite.bootstrapArgs.stream().map(
- (arg) -> {
- if (arg instanceof DexValueMethodHandle) {
- return new DexValueMethodHandle(
- rewriteDexMethodHandle(((DexValueMethodHandle) arg).value, method));
- }
- return arg;
- })
- .collect(Collectors.toList());
-
+ List<DexValue> newArgs = rewriteBootstrapArgs(callSite.bootstrapArgs, method);
if (!newMethodProto.equals(callSite.methodProto)
|| newBootstrapMethod != callSite.bootstrapMethod
|| !newArgs.equals(callSite.bootstrapArgs)) {
@@ -225,6 +218,47 @@
assert code.isConsistentSSA();
}
+ private List<DexValue> rewriteBootstrapArgs(
+ List<DexValue> bootstrapArgs, DexEncodedMethod method) {
+ List<DexValue> newBoostrapArgs = null;
+ boolean changed = false;
+ for (int i = 0; i < bootstrapArgs.size(); i++) {
+ DexValue argument = bootstrapArgs.get(i);
+ DexValue newArgument = null;
+ if (argument instanceof DexValueMethodHandle) {
+ DexMethodHandle oldHandle = ((DexValueMethodHandle) argument).value;
+ DexMethodHandle newHandle = rewriteDexMethodHandle(oldHandle, method);
+ if (newHandle != oldHandle) {
+ newArgument = new DexValueMethodHandle(newHandle);
+ }
+ } else if (argument instanceof DexValueMethodType) {
+ DexProto oldProto = ((DexValueMethodType) argument).value;
+ DexProto newProto =
+ appInfo.dexItemFactory.applyClassMappingToProto(
+ oldProto, graphLense::lookupType, protoFixupCache);
+ if (newProto != oldProto) {
+ newArgument = new DexValueMethodType(newProto);
+ }
+ } else if (argument instanceof DexValueType) {
+ DexType oldType = ((DexValueType) argument).value;
+ DexType newType = graphLense.lookupType(oldType);
+ if (newType != oldType) {
+ newArgument = new DexValueType(newType);
+ }
+ }
+ if (newArgument != null) {
+ if (newBoostrapArgs == null) {
+ newBoostrapArgs = new ArrayList<>(bootstrapArgs.subList(0, i));
+ }
+ newBoostrapArgs.add(newArgument);
+ changed = true;
+ } else if (newBoostrapArgs != null) {
+ newBoostrapArgs.add(argument);
+ }
+ }
+ return changed ? newBoostrapArgs : bootstrapArgs;
+ }
+
private DexMethodHandle rewriteDexMethodHandle(
DexMethodHandle methodHandle, DexEncodedMethod context) {
if (methodHandle.isMethodHandle()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 81407bd..86f3f17 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,11 +29,13 @@
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.google.common.base.Suppliers;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
/**
* Represents lambda class generated for a lambda descriptor in context
@@ -64,6 +66,8 @@
final Target target;
final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<DexProgramClass>(1);
+ private final Supplier<DexProgramClass> lazyDexClass =
+ Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
LambdaClass(LambdaRewriter rewriter, DexType accessedFrom,
DexType lambdaClassType, LambdaDescriptor descriptor) {
@@ -119,7 +123,11 @@
return rewriter.factory.createType(lambdaClassDescriptor.toString());
}
- final DexProgramClass synthesizeLambdaClass() {
+ final DexProgramClass getLambdaClass() {
+ return lazyDexClass.get();
+ }
+
+ private DexProgramClass synthesizeLambdaClass() {
return new DexProgramClass(
type,
null,
@@ -171,7 +179,7 @@
Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
+ new SynthesizedCode(() -> new LambdaMainMethodSourceCode(this, mainMethod)));
// Synthesize bridge methods.
for (DexProto bridgeProto : descriptor.bridges) {
@@ -188,7 +196,7 @@
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
new SynthesizedCode(
- new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
+ () -> new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
}
return methods;
}
@@ -208,7 +216,7 @@
true),
DexAnnotationSet.empty(),
ParameterAnnotationsList.empty(),
- new SynthesizedCode(new LambdaConstructorSourceCode(this)));
+ new SynthesizedCode(() -> new LambdaConstructorSourceCode(this)));
// Class constructor for stateless lambda classes.
if (stateless) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index e7686db..51b8b8a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -141,7 +141,6 @@
if (method.name == deserializeLambdaMethodName &&
method.proto == deserializeLambdaMethodProto) {
assert encoded.accessFlags.isStatic();
- assert encoded.accessFlags.isPrivate();
assert encoded.accessFlags.isSynthetic();
DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
@@ -170,10 +169,19 @@
}
}
+ /**
+ * Returns a synthetic class for desugared lambda or `null` if the `type`
+ * does not represent one. Method can be called concurrently.
+ */
+ public DexProgramClass getLambdaClass(DexType type) {
+ LambdaClass lambdaClass = getKnown(knownLambdaClasses, type);
+ return lambdaClass == null ? null : lambdaClass.getLambdaClass();
+ }
+
/** Generates lambda classes and adds them to the builder. */
public void synthesizeLambdaClasses(Builder<?> builder) {
for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
- DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
+ DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
converter.optimizeSynthesizedClass(synthesizedClass);
builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index d3d1e2f..85f7a39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -60,7 +60,8 @@
@Override
public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
- assert target.isProcessed();
+ // Do nothing. If the method is not yet processed, we still should
+ // be able to build IR for inlining, though.
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
new file mode 100644
index 0000000..dee935e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -0,0 +1,222 @@
+// 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.ir.optimize;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Eliminate redundant field loads.
+ *
+ * <p>Simple algorithm that goes through all blocks in one pass in dominator order and propagates
+ * active field sets across control-flow edges where the target has only one predecessor.
+ */
+// TODO(ager): Evaluate speed/size for computing active field sets in a fixed-point computation.
+public class RedundantFieldLoadElimination {
+
+ private final AppInfo appInfo;
+ private final DexEncodedMethod method;
+ private final IRCode code;
+ private final boolean enableWholeProgramOptimizations;
+ private final DominatorTree dominatorTree;
+
+ // Maps keeping track of fields that have an already loaded value at basic block entry.
+ private final HashMap<BasicBlock, HashMap<FieldAndObject, Instruction>>
+ activeInstanceFieldsAtEntry = new HashMap<>();
+ private final HashMap<BasicBlock, HashMap<DexField, Instruction>> activeStaticFieldsAtEntry =
+ new HashMap<>();
+
+ // Maps keeping track of fields with already loaded values for the current block during
+ // elimination.
+ private HashMap<FieldAndObject, Instruction> activeInstanceFields;
+ private HashMap<DexField, Instruction> activeStaticFields;
+
+ public RedundantFieldLoadElimination(
+ AppInfo appInfo, IRCode code, boolean enableWholeProgramOptimizations) {
+ this.appInfo = appInfo;
+ this.method = code.method;
+ this.code = code;
+ this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
+ dominatorTree = new DominatorTree(code);
+ }
+
+ private static class FieldAndObject {
+ private final DexField field;
+ private final Value object;
+
+ private FieldAndObject(DexField field, Value receiver) {
+ this.field = field;
+ this.object = receiver;
+ }
+
+ @Override
+ public int hashCode() {
+ return field.hashCode() * 7 + object.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof FieldAndObject)) {
+ return false;
+ }
+ FieldAndObject o = (FieldAndObject) other;
+ return o.object == object && o.field == field;
+ }
+ }
+
+ private boolean couldBeVolatile(DexField field) {
+ if (!enableWholeProgramOptimizations && field.getHolder() != method.method.getHolder()) {
+ return true;
+ }
+ DexEncodedField definition = appInfo.definitionFor(field);
+ return definition == null || definition.accessFlags.isVolatile();
+ }
+
+ public void run() {
+ for (BasicBlock block : dominatorTree.getSortedBlocks()) {
+ activeInstanceFields =
+ activeInstanceFieldsAtEntry.containsKey(block)
+ ? activeInstanceFieldsAtEntry.get(block)
+ : new HashMap<>();
+ activeStaticFields =
+ activeStaticFieldsAtEntry.containsKey(block)
+ ? activeStaticFieldsAtEntry.get(block)
+ : new HashMap<>();
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isFieldInstruction()) {
+ DexField field = instruction.asFieldInstruction().getField();
+ if (instruction.isInstancePut() || instruction.isStaticPut()) {
+ killActiveFields(instruction.asFieldInstruction());
+ } else if (couldBeVolatile(field)) {
+ assert instruction.isInstanceGet() || instruction.isStaticGet();
+ killAllActiveFields();
+ } else {
+ assert instruction.isInstanceGet() || instruction.isStaticGet();
+ assert !couldBeVolatile(field);
+ if (instruction.isInstanceGet() && !instruction.outValue().hasLocalInfo()) {
+ Value object = instruction.asInstanceGet().object();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ if (activeInstanceFields.containsKey(fieldAndObject)) {
+ Instruction active = activeInstanceFields.get(fieldAndObject);
+ eliminateRedundantRead(it, instruction, active);
+ } else {
+ activeInstanceFields.put(fieldAndObject, instruction);
+ }
+ } else if (instruction.isStaticGet() && !instruction.outValue().hasLocalInfo()) {
+ if (activeStaticFields.containsKey(field)) {
+ Instruction active = activeStaticFields.get(field);
+ eliminateRedundantRead(it, instruction, active);
+ } else {
+ // A field get on a different class can cause <clinit> to run and change static
+ // field values.
+ killActiveFields(instruction.asFieldInstruction());
+ activeStaticFields.put(field, instruction);
+ }
+ }
+ }
+ }
+ if (instruction.isMonitor() || instruction.isInvokeMethod()) {
+ killAllActiveFields();
+ }
+ }
+ propagateActiveFieldsFrom(block);
+ }
+ assert code.isConsistentSSA();
+ }
+
+ private void propagateActiveFieldsFrom(BasicBlock block) {
+ for (BasicBlock successor : block.getSuccessors()) {
+ // Allow propagation across exceptional edges, just be careful not to propagate if the
+ // throwing instruction is a field instruction.
+ if (successor.getPredecessors().size() == 1) {
+ if (block.hasCatchSuccessor(successor)) {
+ Instruction exceptionalExit = block.exceptionalExit();
+ if (exceptionalExit != null && exceptionalExit.isFieldInstruction()) {
+ killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
+ }
+ }
+ assert !activeInstanceFieldsAtEntry.containsKey(successor);
+ activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFields));
+ assert !activeStaticFieldsAtEntry.containsKey(successor);
+ activeStaticFieldsAtEntry.put(successor, new HashMap<>(activeStaticFields));
+ }
+ }
+ }
+
+ private void killAllActiveFields() {
+ activeInstanceFields.clear();
+ activeStaticFields.clear();
+ }
+
+ private void killActiveFields(FieldInstruction instruction) {
+ DexField field = instruction.getField();
+ if (instruction.isInstancePut()) {
+ // Remove all the field/object pairs that refer to this field to make sure
+ // that we are conservative.
+ List<FieldAndObject> keysToRemove = new ArrayList<>();
+ for (FieldAndObject key : activeInstanceFields.keySet()) {
+ if (key.field == field) {
+ keysToRemove.add(key);
+ }
+ }
+ keysToRemove.forEach((k) -> activeInstanceFields.remove(k));
+ } else if (instruction.isInstanceGet()) {
+ Value object = instruction.asInstanceGet().object();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ activeInstanceFields.remove(fieldAndObject);
+ } else if (instruction.isStaticPut()) {
+ if (field.clazz != code.method.method.holder) {
+ // Accessing a static field on a different object could cause <clinit> to run which
+ // could modify any static field on any other object.
+ activeStaticFields.clear();
+ } else {
+ activeStaticFields.remove(field);
+ }
+ } else if (instruction.isStaticGet()) {
+ if (field.clazz != code.method.method.holder) {
+ // Accessing a static field on a different object could cause <clinit> to run which
+ // could modify any static field on any other object.
+ activeStaticFields.clear();
+ }
+ }
+ }
+
+ // If a field get instruction throws an exception it did not have an effect on the
+ // value of the field. Therefore, when propagating across exceptional edges for a
+ // field get instruction we have to exclude that field from the set of known
+ // field values.
+ private void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
+ DexField field = instruction.getField();
+ if (instruction.isInstanceGet()) {
+ Value object = instruction.asInstanceGet().object();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ activeInstanceFields.remove(fieldAndObject);
+ } else if (instruction.isStaticGet()) {
+ activeStaticFields.remove(field);
+ }
+ }
+
+ private void eliminateRedundantRead(
+ InstructionListIterator it, Instruction redundant, Instruction active) {
+ redundant.outValue().replaceUsers(active.outValue());
+ it.removeOrReplaceByDebugLocalRead();
+ active.outValue().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 5bf2c74..3a2371d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -8,10 +8,10 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -27,15 +27,18 @@
public final class ClassInliner {
private final DexItemFactory factory;
+ private final LambdaRewriter lambdaRewriter;
private final int totalMethodInstructionLimit;
- private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>();
public interface InlinerAction {
void inline(Map<InvokeMethod, InliningInfo> methods);
}
- public ClassInliner(DexItemFactory factory, int totalMethodInstructionLimit) {
+ public ClassInliner(DexItemFactory factory,
+ LambdaRewriter lambdaRewriter, int totalMethodInstructionLimit) {
this.factory = factory;
+ this.lambdaRewriter = lambdaRewriter;
this.totalMethodInstructionLimit = totalMethodInstructionLimit;
}
@@ -142,8 +145,8 @@
while (rootsIterator.hasNext()) {
Instruction root = rootsIterator.next();
InlineCandidateProcessor processor =
- new InlineCandidateProcessor(factory, appInfo,
- type -> isClassEligible(appInfo, type),
+ new InlineCandidateProcessor(factory, appInfo, lambdaRewriter,
+ clazz -> isClassEligible(appInfo, clazz),
isProcessedConcurrently, method, root);
// Assess eligibility of instance and class.
@@ -180,7 +183,7 @@
} while (repeat);
}
- private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+ private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
Boolean eligible = knownClasses.get(clazz);
if (eligible == null) {
Boolean computed = computeClassEligible(appInfo, clazz);
@@ -195,15 +198,14 @@
// - is not an abstract class or interface
// - does not declare finalizer
// - does not trigger any static initializers except for its own
- private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
- DexClass definition = appInfo.definitionFor(clazz);
- if (definition == null || definition.isLibraryClass() ||
- definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+ private boolean computeClassEligible(AppInfo appInfo, DexClass clazz) {
+ if (clazz == null || clazz.isLibraryClass() ||
+ clazz.accessFlags.isAbstract() || clazz.accessFlags.isInterface()) {
return false;
}
// Class must not define finalizer.
- for (DexEncodedMethod method : definition.virtualMethods()) {
+ for (DexEncodedMethod method : clazz.virtualMethods()) {
if (method.method.name == factory.finalizeMethodName &&
method.method.proto == factory.objectMethods.finalize.proto) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 57ce42f..5fedf37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -53,7 +54,8 @@
private final DexItemFactory factory;
private final AppInfoWithLiveness appInfo;
- private final Predicate<DexType> isClassEligible;
+ private final LambdaRewriter lambdaRewriter;
+ private final Predicate<DexClass> isClassEligible;
private final Predicate<DexEncodedMethod> isProcessedConcurrently;
private final DexEncodedMethod method;
private final Instruction root;
@@ -61,6 +63,7 @@
private Value eligibleInstance;
private DexType eligibleClass;
private DexClass eligibleClassDefinition;
+ private boolean isDesugaredLambda;
private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance
= new IdentityHashMap<>();
@@ -73,10 +76,11 @@
InlineCandidateProcessor(
DexItemFactory factory, AppInfoWithLiveness appInfo,
- Predicate<DexType> isClassEligible,
+ LambdaRewriter lambdaRewriter, Predicate<DexClass> isClassEligible,
Predicate<DexEncodedMethod> isProcessedConcurrently,
DexEncodedMethod method, Instruction root) {
this.factory = factory;
+ this.lambdaRewriter = lambdaRewriter;
this.isClassEligible = isClassEligible;
this.method = method;
this.root = root;
@@ -99,6 +103,11 @@
eligibleClass = isNewInstance() ?
root.asNewInstance().clazz : root.asStaticGet().getField().type;
eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+ if (eligibleClassDefinition == null && lambdaRewriter != null) {
+ // Check if the class is synthesized for a desugared lambda
+ eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
+ isDesugaredLambda = eligibleClassDefinition != null;
+ }
return eligibleClassDefinition != null;
}
@@ -114,7 +123,7 @@
// * class has class initializer marked as TrivialClassInitializer, and
// class initializer initializes the field we are reading here.
boolean isClassAndUsageEligible() {
- if (!isClassEligible.test(eligibleClass)) {
+ if (!isClassEligible.test(eligibleClassDefinition)) {
return false;
}
@@ -129,6 +138,11 @@
assert root.isStaticGet();
+ // We know that desugared lambda classes satisfy eligibility requirements.
+ if (isDesugaredLambda) {
+ return true;
+ }
+
// Checking if we can safely inline class implemented following singleton-like
// pattern, by which we assume a static final field holding on to the reference
// initialized in class constructor.
@@ -444,7 +458,7 @@
: "Inlined constructor? [invoke: " + initInvoke +
", expected class: " + eligibleClass + "]";
- DexEncodedMethod definition = appInfo.definitionFor(init);
+ DexEncodedMethod definition = findSingleTarget(init, true);
if (definition == null || isProcessedConcurrently.test(definition)) {
return null;
}
@@ -455,6 +469,12 @@
return null;
}
+ if (isDesugaredLambda) {
+ // Lambda desugaring synthesizes eligible constructors.
+ markSizeForInlining(definition);
+ return new InliningInfo(definition, eligibleClass);
+ }
+
// If the superclass of the initializer is NOT java.lang.Object, the super class
// initializer being called must be classified as TrivialInstanceInitializer.
//
@@ -499,7 +519,7 @@
private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
- DexEncodedMethod singleTarget = findSingleTarget(callee);
+ DexEncodedMethod singleTarget = findSingleTarget(callee, false);
if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) {
return null;
}
@@ -507,6 +527,16 @@
return null; // Don't inline itself.
}
+ if (isDesugaredLambda) {
+ // If this is the call to method of the desugared lambda, we consider only calls
+ // to main lambda method eligible (for both direct and indirect calls).
+ if (singleTarget.accessFlags.isBridge()) {
+ return null;
+ }
+ markSizeForInlining(singleTarget);
+ return new InliningInfo(singleTarget, eligibleClass);
+ }
+
OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
@@ -652,6 +682,9 @@
private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
DexType inlineeHolder = inlinee.method.holder;
+ if (isDesugaredLambda && inlineeHolder == eligibleClass) {
+ return true;
+ }
if (appInfo.isPinned(inlineeHolder)) {
return false;
}
@@ -674,14 +707,16 @@
return root.isNewInstance();
}
- private DexEncodedMethod findSingleTarget(DexMethod callee) {
+ private DexEncodedMethod findSingleTarget(DexMethod callee, boolean isDirect) {
// We don't use computeSingleTarget(...) on invoke since it sometimes fails to
// find the single target, while this code may be more successful since we exactly
// know what is the actual type of the receiver.
// Note that we also intentionally limit ourselves to methods directly defined in
// the instance's class. This may be improved later.
- return eligibleClassDefinition.lookupVirtualMethod(callee);
+ return isDirect
+ ? eligibleClassDefinition.lookupDirectMethod(callee)
+ : eligibleClassDefinition.lookupVirtualMethod(callee);
}
private void removeInstruction(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index a5807c3..7835da2 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -10,25 +10,33 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import java.util.function.Consumer;
+import java.util.function.Supplier;
public final class SynthesizedCode extends Code {
- private final SourceCode sourceCode;
+ private final Supplier<SourceCode> sourceCodeProvider;
private final Consumer<UseRegistry> registryCallback;
- public SynthesizedCode(SourceCode sourceCode) {
- this.sourceCode = sourceCode;
+ public SynthesizedCode(SourceCode sourceCodeProvider) {
+ this(() -> sourceCodeProvider);
+ }
+
+ public SynthesizedCode(Supplier<SourceCode> sourceCodeProvider) {
+ this.sourceCodeProvider = sourceCodeProvider;
this.registryCallback = SynthesizedCode::registerReachableDefinitionsDefault;
}
- public SynthesizedCode(SourceCode sourceCode, Consumer<UseRegistry> callback) {
- this.sourceCode = sourceCode;
+ public SynthesizedCode(
+ SourceCode sourceCode, Consumer<UseRegistry> callback) {
+ this.sourceCodeProvider = () -> sourceCode;
this.registryCallback = callback;
}
@@ -40,7 +48,15 @@
@Override
public final IRCode buildIR(
DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
- return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
+ return new IRBuilder(encodedMethod, appInfo, sourceCodeProvider.get(), options).build();
+ }
+
+ @Override
+ public IRCode buildInliningIR(
+ DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options,
+ ValueNumberGenerator valueNumberGenerator, Position callerPosition, Origin origin) {
+ return new IRBuilder(encodedMethod, appInfo,
+ sourceCodeProvider.get(), options, valueNumberGenerator).build();
}
@Override
@@ -59,17 +75,16 @@
@Override
protected final int computeHashCode() {
- return sourceCode.hashCode();
+ throw new Unreachable();
}
@Override
protected final boolean computeEquals(Object other) {
- return other instanceof SynthesizedCode &&
- this.sourceCode.equals(((SynthesizedCode) other).sourceCode);
+ throw new Unreachable();
}
@Override
public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
- return "SynthesizedCode: " + sourceCode.toString();
+ return "SynthesizedCode";
}
}
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 494eda8..6a12b60 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -155,6 +155,8 @@
* its implementation may be removed and it may be marked abstract.
*/
private final SetWithReason<DexEncodedMethod> targetedMethods = new SetWithReason<>();
+ /** Set of virtual methods that are the immediate target of an invoke-direct. */
+ private final Set<DexMethod> virtualMethodsTargetedByInvokeDirect = Sets.newIdentityHashSet();
/**
* Set of methods that belong to live classes and can be reached by invokes. These need to be
* kept.
@@ -709,6 +711,13 @@
DexEncodedMethod target = appInfo.dispatchDirectInvoke(resolutionResult);
if (target != null) {
markDirectStaticOrConstructorMethodAsLive(target, reason);
+
+ // It is valid to have an invoke-direct instruction in a default interface method that
+ // targets another default method in the same interface (see testInvokeSpecialToDefault-
+ // Method). In a class, that would lead to a verification error.
+ if (target.isVirtualMethod()) {
+ virtualMethodsTargetedByInvokeDirect.add(target.method);
+ }
}
}
@@ -1587,6 +1596,8 @@
* removed.
*/
final SortedSet<DexMethod> targetedMethods;
+ /** Set of virtual methods that are the immediate target of an invoke-direct. */
+ final SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect;
/**
* Set of methods that belong to live classes and can be reached by invokes. These need to be
* kept.
@@ -1704,6 +1715,9 @@
ImmutableSortedSet.copyOf(
PresortedComparable<DexType>::slowCompareTo, enqueuer.instantiatedLambdas.getItems());
this.targetedMethods = toSortedDescriptorSet(enqueuer.targetedMethods.getItems());
+ this.virtualMethodsTargetedByInvokeDirect =
+ ImmutableSortedSet.copyOf(
+ DexMethod::slowCompareTo, enqueuer.virtualMethodsTargetedByInvokeDirect);
this.liveMethods = toSortedDescriptorSet(enqueuer.liveMethods.getItems());
this.liveFields = toSortedDescriptorSet(enqueuer.liveFields.getItems());
this.instanceFieldReads = enqueuer.collectInstanceFieldsRead();
@@ -1744,6 +1758,7 @@
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
+ this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
this.liveMethods = previous.liveMethods;
this.liveFields = previous.liveFields;
this.instanceFieldReads = previous.instanceFieldReads;
@@ -1783,6 +1798,8 @@
this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
this.targetedMethods = rewriteMethodsConservatively(previous.targetedMethods, lense);
+ this.virtualMethodsTargetedByInvokeDirect =
+ rewriteMethodsConservatively(previous.virtualMethodsTargetedByInvokeDirect, lense);
this.liveMethods = rewriteMethodsConservatively(previous.liveMethods, lense);
this.liveFields = rewriteItems(previous.liveFields, lense::lookupField);
this.instanceFieldReads =
@@ -1834,6 +1851,7 @@
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
+ this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
this.liveMethods = previous.liveMethods;
this.liveFields = previous.liveFields;
this.instanceFieldReads = previous.instanceFieldReads;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 0840d91..4d072c4 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -81,6 +81,8 @@
RESOLUTION_FOR_FIELDS_MAY_CHANGE,
RESOLUTION_FOR_METHODS_MAY_CHANGE,
STATIC_INITIALIZERS,
+ UNHANDLED_INVOKE_DIRECT,
+ UNHANDLED_INVOKE_SUPER,
UNSAFE_INLINING,
UNSUPPORTED_ATTRIBUTES;
@@ -121,6 +123,12 @@
case STATIC_INITIALIZERS:
message = "merging of static initializers are not supported";
break;
+ case UNHANDLED_INVOKE_DIRECT:
+ message = "a virtual method is targeted by an invoke-direct instruction";
+ break;
+ case UNHANDLED_INVOKE_SUPER:
+ message = "it may change the semantics of an invoke-super instruction";
+ break;
case UNSAFE_INLINING:
message = "force-inlining might fail";
break;
@@ -217,10 +225,15 @@
// }
// }
for (DexMethod signature : appInfo.brokenSuperInvokes) {
- DexClass targetClass = appInfo.definitionFor(signature.holder);
- if (targetClass != null && targetClass.isProgramClass()) {
- pinnedTypes.add(signature.holder);
- }
+ markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_SUPER);
+ }
+
+ // It is valid to have an invoke-direct instruction in a default interface method that targets
+ // another default method in the same interface (see InterfaceMethodDesugaringTests.testInvoke-
+ // SpecialToDefaultMethod). However, in a class, that would lead to a verification error.
+ // Therefore, we disallow merging such interfaces into their subtypes.
+ for (DexMethod signature : appInfo.virtualMethodsTargetedByInvokeDirect) {
+ markTypeAsPinned(signature.holder, AbortReason.UNHANDLED_INVOKE_DIRECT);
}
}
@@ -882,50 +895,61 @@
}
private void redirectSuperCallsInTarget(DexMethod oldTarget, DexMethod newTarget) {
- // If we merge class B into class C, and class C contains an invocation super.m(), then it
- // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
- // C.m$B denotes the direct method that has been created in C for B.m). In particular, there
- // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is
- // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()".
- //
- // We handle this by adding a mapping for [target] and all of its supertypes.
- DexClass holder = target;
- while (holder != null && holder.isProgramClass()) {
- DexMethod signatureInHolder =
- application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name);
- // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
- boolean resolutionSucceeds =
- holder.lookupVirtualMethod(signatureInHolder) != null
- || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
- if (resolutionSucceeds) {
- deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInHolder, newTarget, target.type);
- } else {
- break;
- }
+ if (source.accessFlags.isInterface()) {
+ // If we merge a default interface method from interface I to its subtype C, then we need
+ // to rewrite invocations on the form "invoke-super I.m()" to "invoke-direct C.m$I()".
+ //
+ // Unlike when we merge a class into its subclass (the else-branch below), we should *not*
+ // rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()",
+ // if I has a supertype J. This is due to the fact that invoke-super instructions that
+ // resolve to a method on an interface never hit an implementation below that interface.
+ deferredRenamings.mapVirtualMethodToDirectInType(oldTarget, newTarget, target.type);
+ } else {
+ // If we merge class B into class C, and class C contains an invocation super.m(), then it
+ // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
+ // C.m$B denotes the direct method that has been created in C for B.m). In particular, there
+ // might be an instruction "invoke-super A.m()" in C that resolves to B.m at runtime (A is
+ // a superclass of B), which also needs to be rewritten to "invoke-direct C.m$B()".
+ //
+ // We handle this by adding a mapping for [target] and all of its supertypes.
+ DexClass holder = target;
+ while (holder != null && holder.isProgramClass()) {
+ DexMethod signatureInHolder =
+ application.dexItemFactory.createMethod(holder.type, oldTarget.proto, oldTarget.name);
+ // Only rewrite the invoke-super call if it does not lead to a NoSuchMethodError.
+ boolean resolutionSucceeds =
+ holder.lookupVirtualMethod(signatureInHolder) != null
+ || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
+ if (resolutionSucceeds) {
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ signatureInHolder, newTarget, target.type);
+ } else {
+ break;
+ }
- // Consider that A gets merged into B and B's subclass C gets merged into D. Instructions
- // on the form "invoke-super {B,C,D}.m()" in D are changed into "invoke-direct D.m$C()" by
- // the code above. However, instructions on the form "invoke-super A.m()" should also be
- // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes
- // that have been merged into [holder].
- Set<DexType> mergedTypes = mergedClassesInverse.get(holder.type);
- if (mergedTypes != null) {
- for (DexType type : mergedTypes) {
- DexMethod signatureInType =
- application.dexItemFactory.createMethod(type, oldTarget.proto, oldTarget.name);
- // Resolution would have succeeded if the method used to be in [type], or if one of
- // its super classes declared the method.
- boolean resolutionSucceededBeforeMerge =
- renamedMembersLense.hasMappingForSignatureInContext(holder.type, signatureInType)
- || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
- if (resolutionSucceededBeforeMerge) {
- deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInType, newTarget, target.type);
+ // Consider that A gets merged into B and B's subclass C gets merged into D. Instructions
+ // on the form "invoke-super {B,C,D}.m()" in D are changed into "invoke-direct D.m$C()" by
+ // the code above. However, instructions on the form "invoke-super A.m()" should also be
+ // changed into "invoke-direct D.m$C()". This is achieved by also considering the classes
+ // that have been merged into [holder].
+ Set<DexType> mergedTypes = mergedClassesInverse.get(holder.type);
+ if (mergedTypes != null) {
+ for (DexType type : mergedTypes) {
+ DexMethod signatureInType =
+ application.dexItemFactory.createMethod(type, oldTarget.proto, oldTarget.name);
+ // Resolution would have succeeded if the method used to be in [type], or if one of
+ // its super classes declared the method.
+ boolean resolutionSucceededBeforeMerge =
+ renamedMembersLense.hasMappingForSignatureInContext(holder.type, signatureInType)
+ || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
+ if (resolutionSucceededBeforeMerge) {
+ deferredRenamings.mapVirtualMethodToDirectInType(
+ signatureInType, newTarget, target.type);
+ }
}
}
+ holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null;
}
- holder = holder.superType != null ? appInfo.definitionFor(holder.superType) : null;
}
}
diff --git a/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
new file mode 100644
index 0000000..781d2b4
--- /dev/null
+++ b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
@@ -0,0 +1,57 @@
+// 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 uninitializedfinal;
+
+// Test that leaks an instance before its final field has been initialized to a thread that
+// reads that field. This tests that redundant field load elimination does not eliminate
+// field reads (even of final fields) that cross a monitor operation.
+public class UninitializedFinalFieldLeak {
+
+ public static class PollingThread extends Thread {
+ public int result = 0;
+ UninitializedFinalFieldLeak f;
+
+ PollingThread(UninitializedFinalFieldLeak f) {
+ this.f = f;
+ }
+
+ // Read the field a number of times. Then lock on the object to await field initialization.
+ public void run() {
+ result += f.i;
+ result += f.i;
+ result += f.i;
+ f.threadReadsDone = true;
+ synchronized (f) {
+ result += f.i;
+ }
+ // The right result is 42. Reading the uninitialized 0 three times and then
+ // reading the initialized value. It is safe to remove the two redundant loads
+ // before the monitor operation.
+ System.out.println(result);
+ }
+ }
+
+ public final int i;
+ public volatile boolean threadReadsDone = false;
+
+ public UninitializedFinalFieldLeak() throws InterruptedException {
+ // Leak the object to a thread and start the thread with the lock on the object taken.
+ // Then allow the other thread to run and read the uninitialized field.
+ // Finally, initialize the field and release the lock.
+ PollingThread t = new PollingThread(this);
+ synchronized (this) {
+ t.start();
+ while (!threadReadsDone) {
+ Thread.yield();
+ }
+ i = 42;
+ }
+ t.join();
+ }
+
+ public static void main(String[] args) throws InterruptedException {
+ new UninitializedFinalFieldLeak();
+ }
+}
diff --git a/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java b/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.java
new file mode 100644
index 0000000..7a0e325
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/NestedDefaultInterfaceMethodsTest.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 classmerging;
+
+public class NestedDefaultInterfaceMethodsTest {
+
+ public static void main(String[] args) {
+ new C().m();
+ }
+
+ public interface A {
+
+ default void m() {
+ System.out.println("In A.m()");
+ }
+ }
+
+ public interface B extends A {
+
+ @Override
+ default void m() {
+ System.out.println("In B.m()");
+ A.super.m();
+ }
+ }
+
+ public static class C implements B {
+
+ @Override
+ public void m() {
+ System.out.println("In C.m()");
+ B.super.m();
+ }
+ }
+}
diff --git a/src/test/examplesAndroidO/classmerging/keep-rules.txt b/src/test/examplesAndroidO/classmerging/keep-rules.txt
index fc91808..4df182e 100644
--- a/src/test/examplesAndroidO/classmerging/keep-rules.txt
+++ b/src/test/examplesAndroidO/classmerging/keep-rules.txt
@@ -10,6 +10,9 @@
-keep public class classmerging.MergeDefaultMethodIntoClassTest {
public static void main(...);
}
+-keep public class classmerging.NestedDefaultInterfaceMethodsTest {
+ public static void main(...);
+}
# TODO(herhut): Consider supporting merging of inner-class attributes.
# -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index f20335b..be9261f 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -4,11 +4,15 @@
package com.android.tools.r8;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
import com.android.tools.r8.utils.OffOrAuto;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -94,6 +98,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -101,6 +106,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
.run();
}
@@ -112,6 +118,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -119,6 +126,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
.run();
}
@@ -131,6 +139,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -139,6 +148,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
@@ -151,6 +161,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
.run();
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -159,9 +170,21 @@
.withOptionConsumer(opts -> opts.enableClassInlining = true)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
.run();
}
+ private void checkLambdaCount(DexInspector inspector, int expectedCount, String prefix) {
+ int count = 0;
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ if (clazz.isSynthesizedJavaLambdaClass() &&
+ clazz.getOriginalName().startsWith(prefix)) {
+ count++;
+ }
+ }
+ assertEquals(expectedCount, count);
+ }
+
class R8TestRunner extends TestRunner<R8TestRunner> {
R8TestRunner(String testName, String packageName, String mainClass) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 36f7594..221d427 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -45,9 +45,11 @@
"instancevariable.InstanceVariable",
"instanceofstring.InstanceofString",
"invoke.Invoke",
+ "invokeempty.InvokeEmpty",
"jumbostring.JumboString",
"loadconst.LoadConst",
"loop.UdpServer",
+ "nestedtrycatches.NestedTryCatches",
"newarray.NewArray",
"regalloc.RegAlloc",
"returns.Returns",
@@ -58,9 +60,7 @@
"throwing.Throwing",
"trivial.Trivial",
"trycatch.TryCatch",
- "nestedtrycatches.NestedTryCatches",
"trycatchmany.TryCatchMany",
- "invokeempty.InvokeEmpty",
"regress.Regress",
"regress2.Regress2",
"regress_37726195.Regress",
@@ -82,6 +82,7 @@
"enclosingmethod_proguarded.Main",
"interfaceinlining.Main",
"switchmaps.Switches",
+ "uninitializedfinal.UninitializedFinalFieldLeak",
};
List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 7b4f199..8adddfd 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -166,6 +166,17 @@
}
/**
+ * Copy test classes to the specified directory.
+ */
+ protected void copyTestClasses(Path dest, Class... classes) throws IOException {
+ for (Class clazz : classes) {
+ Path path = dest.resolve(clazz.getCanonicalName().replace('.', '/') + ".class");
+ Files.createDirectories(path.getParent());
+ Files.copy(ToolHelper.getClassFileForTestClass(clazz), path);
+ }
+ }
+
+ /**
* Create a temporary JAR file containing the specified test classes.
*/
protected Path jarTestClasses(Class... classes) throws IOException {
@@ -518,7 +529,11 @@
}
protected ProcessResult runOnJavaRaw(String main, byte[]... classes) throws IOException {
- Path file = writeToZip(Arrays.asList(classes));
+ return runOnJavaRaw(main, Arrays.asList(classes));
+ }
+
+ protected ProcessResult runOnJavaRaw(String main, List<byte[]> classes) throws IOException {
+ Path file = writeToZip(classes);
return ToolHelper.runJavaNoVerify(file, main);
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 200a1e7..2767df7 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -474,8 +474,9 @@
public static byte[] getClassAsBytes(Class clazz) throws IOException {
String s = clazz.getSimpleName() + ".class";
Class outer = clazz.getEnclosingClass();
- if (outer != null) {
+ while (outer != null) {
s = outer.getSimpleName() + '$' + s;
+ outer = outer.getEnclosingClass();
}
return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
}
@@ -745,10 +746,6 @@
return parts;
}
- public static Path getPackageDirectoryForTestClass(Class clazz) {
- return getPackageDirectoryForTestPackage(clazz.getPackage());
- }
-
public static List<Path> getClassFilesForTestPackage(Package pkg) throws IOException {
Path dir = ToolHelper.getPackageDirectoryForTestPackage(pkg);
return Files.walk(dir)
@@ -762,6 +759,11 @@
Paths.get("", parts.toArray(new String[parts.size() - 1])));
}
+ public static Path getFileNameForTestClass(Class clazz) {
+ List<String> parts = getNamePartsForTestClass(clazz);
+ return Paths.get("", parts.toArray(new String[parts.size() - 1]));
+ }
+
public static String getJarEntryForTestClass(Class clazz) {
List<String> parts = getNamePartsForTestClass(clazz);
return String.join("/", parts);
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index f28c86f..b6489b0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -251,6 +251,50 @@
}
@Test
+ public void testNestedDefaultInterfaceMethodsTest() throws Exception {
+ String main = "classmerging.NestedDefaultInterfaceMethodsTest";
+ Path[] programFiles =
+ new Path[] {
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$A.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$B.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$C.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.NestedDefaultInterfaceMethodsTest",
+ "classmerging.NestedDefaultInterfaceMethodsTest$B",
+ "classmerging.NestedDefaultInterfaceMethodsTest$C");
+ runTest(
+ main, programFiles, preservedClassNames::contains, getProguardConfig(JAVA8_EXAMPLE_KEEP));
+ }
+
+ @Test
+ public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Exception {
+ String main = "classmerging.NestedDefaultInterfaceMethodsTest";
+ Path[] programFiles =
+ new Path[] {
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$A.class"),
+ JAVA8_CF_DIR.resolve("NestedDefaultInterfaceMethodsTest$B.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.NestedDefaultInterfaceMethodsTest",
+ "classmerging.NestedDefaultInterfaceMethodsTest$B",
+ "classmerging.NestedDefaultInterfaceMethodsTest$C");
+ runTestOnInput(
+ main,
+ AndroidApp.builder()
+ .addProgramFiles(programFiles)
+ .addClassProgramData(
+ NestedDefaultInterfaceMethodsTestDump.CDump.dump(), Origin.unknown())
+ .build(),
+ preservedClassNames::contains,
+ getProguardConfig(JAVA8_EXAMPLE_KEEP));
+ }
+
+ @Test
public void testPinnedParameterTypes() throws Exception {
String main = "classmerging.PinnedParameterTypesTest";
Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java b/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java
new file mode 100644
index 0000000..e55802d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/NestedDefaultInterfaceMethodsTestDump.java
@@ -0,0 +1,76 @@
+// 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.classmerging;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class NestedDefaultInterfaceMethodsTestDump {
+
+ // Generated by running ./tools/asmifier.py build/test/examplesAndroidO/classes/classmerging/-
+ // NestedDefaultInterfaceMethodsTest\$C.class, and changing "invoke-special B.m()" to "invoke-
+ // special A.m()".
+ public static class CDump implements Opcodes {
+
+ public static byte[] dump() {
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "classmerging/NestedDefaultInterfaceMethodsTest$C",
+ null,
+ "java/lang/Object",
+ new String[] {"classmerging/NestedDefaultInterfaceMethodsTest$B"});
+
+ cw.visitInnerClass(
+ "classmerging/NestedDefaultInterfaceMethodsTest$C",
+ "classmerging/NestedDefaultInterfaceMethodsTest",
+ "C",
+ ACC_PUBLIC + ACC_STATIC);
+
+ cw.visitInnerClass(
+ "classmerging/NestedDefaultInterfaceMethodsTest$B",
+ "classmerging/NestedDefaultInterfaceMethodsTest",
+ "B",
+ ACC_PUBLIC + ACC_STATIC + ACC_ABSTRACT + ACC_INTERFACE);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "m", "()V", null, null);
+ mv.visitCode();
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("In C.m()");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitVarInsn(ALOAD, 0);
+ // The signature "classmerging/NestedDefaultInterfaceMethodsTest$B" has been changed to
+ // "classmerging/NestedDefaultInterfaceMethodsTest$A".
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "classmerging/NestedDefaultInterfaceMethodsTest$A", "m", "()V", true);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java
new file mode 100644
index 0000000..68277dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java
@@ -0,0 +1,41 @@
+// 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 ArrayDimensionGreaterThanSevenTest {
+
+ public static float foo(int x) {
+ try {
+ float[] fs1 = new float[] {42f};
+ float[][] fs2 = new float[][] {fs1};
+ float[][][] fs3 = new float[][][] {fs2};
+ float[][][][] fs4 = new float[][][][] {fs3};
+ float[][][][][] fs5 = new float[][][][][] {fs4};
+ float[][][][][][] fs6 = new float[][][][][][] {fs5};
+ float[][][][][][][] fs7 = new float[][][][][][][] {fs6};
+ float[][][][][][][][] fs8 = new float[][][][][][][][] {fs7};
+ while (x-- > 0) {
+ try {
+ fs8 = x == 0 ? fs8 : null;
+ fs7 = x == 1 ? fs8[1] : fs8[0];
+ fs6 = x == 2 ? fs7[1] : fs7[0];
+ fs5 = x == 3 ? fs6[1] : fs6[0];
+ fs4 = x == 4 ? fs5[1] : fs5[0];
+ fs3 = x == 5 ? fs4[1] : fs4[0];
+ fs2 = x == 6 ? fs3[1] : fs3[0];
+ fs1 = x == 7 ? fs2[1] : fs2[0];
+ } catch (NullPointerException e) {
+ System.out.println("null pointer");
+ }
+ }
+ } catch (RuntimeException e) {
+ return -1f;
+ }
+ return 42;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo(args.length + 1));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
new file mode 100644
index 0000000..5cd652d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
@@ -0,0 +1,590 @@
+package com.android.tools.r8.debug;
+
+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 ArrayDimensionGreaterThanSevenTestDump implements Opcodes {
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "foo", "(I)F", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ Label l1 = new Label();
+ Label l2 = new Label();
+ mv.visitTryCatchBlock(l0, l1, l2, "java/lang/NullPointerException");
+ Label l3 = new Label();
+ Label l4 = new Label();
+ Label l5 = new Label();
+ mv.visitTryCatchBlock(l3, l4, l5, "java/lang/RuntimeException");
+ mv.visitLabel(l3);
+ mv.visitInsn(ICONST_1);
+ mv.visitIntInsn(NEWARRAY, T_FLOAT);
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitLdcInsn(new Float("42.0"));
+ mv.visitInsn(FASTORE);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 3);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 4);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 5);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 5);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 6);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 6);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 7);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 7);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 8);
+ Label l6 = new Label();
+ mv.visitLabel(l6);
+ // mv.visitFrame(Opcodes.F_FULL, 9, new Object[] {Opcodes.INTEGER, "[F", "[[F", "[[[F",
+ // "[[[[F", "[[[[[F", "[[[[[[F", "[[[[[[[F", "[[[[[[[[F"}, 0, new Object[] {});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitIincInsn(0, -1);
+ mv.visitJumpInsn(IFLE, l4);
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ILOAD, 0);
+ Label l7 = new Label();
+ mv.visitJumpInsn(IFNE, l7);
+ mv.visitVarInsn(ALOAD, 8);
+ Label l8 = new Label();
+ mv.visitJumpInsn(GOTO, l8);
+ mv.visitLabel(l7);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitInsn(ACONST_NULL);
+ mv.visitTypeInsn(CHECKCAST, "[[[[[[[[F");
+ mv.visitLabel(l8);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[[[[F"});
+ mv.visitVarInsn(ASTORE, 8);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_1);
+ Label l9 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l9);
+ mv.visitVarInsn(ALOAD, 8);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l10 = new Label();
+ mv.visitJumpInsn(GOTO, l10);
+ mv.visitLabel(l9);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 8);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l10);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[[[F"});
+ mv.visitVarInsn(ASTORE, 7);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_2);
+ Label l11 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l11);
+ mv.visitVarInsn(ALOAD, 7);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l12 = new Label();
+ mv.visitJumpInsn(GOTO, l12);
+ mv.visitLabel(l11);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 7);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l12);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[[F"});
+ mv.visitVarInsn(ASTORE, 6);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_3);
+ Label l13 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l13);
+ mv.visitVarInsn(ALOAD, 6);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l14 = new Label();
+ mv.visitJumpInsn(GOTO, l14);
+ mv.visitLabel(l13);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 6);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l14);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[F"});
+ mv.visitVarInsn(ASTORE, 5);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_4);
+ Label l15 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l15);
+ mv.visitVarInsn(ALOAD, 5);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l16 = new Label();
+ mv.visitJumpInsn(GOTO, l16);
+ mv.visitLabel(l15);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 5);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l16);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[F"});
+ mv.visitVarInsn(ASTORE, 4);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_5);
+ Label l17 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l17);
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l18 = new Label();
+ mv.visitJumpInsn(GOTO, l18);
+ mv.visitLabel(l17);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l18);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[F"});
+ mv.visitVarInsn(ASTORE, 3);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitIntInsn(BIPUSH, 6);
+ Label l19 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l19);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l20 = new Label();
+ mv.visitJumpInsn(GOTO, l20);
+ mv.visitLabel(l19);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l20);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[F"});
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitIntInsn(BIPUSH, 7);
+ Label l21 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l21);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l22 = new Label();
+ mv.visitJumpInsn(GOTO, l22);
+ mv.visitLabel(l21);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l22);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[F"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(l1);
+ mv.visitJumpInsn(GOTO, l6);
+ mv.visitLabel(l2);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]
+ // {"java/lang/NullPointerException"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"java/lang/NullPointerException"});
+ mv.visitVarInsn(ASTORE, 9);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("null pointer");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitJumpInsn(GOTO, l6);
+ mv.visitLabel(l4);
+ // mv.visitFrame(Opcodes.F_FULL, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+ mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+ Label l23 = new Label();
+ mv.visitJumpInsn(GOTO, l23);
+ mv.visitLabel(l5);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/RuntimeException"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 1,
+ new Object[] {Opcodes.INTEGER},
+ 1,
+ new Object[] {"java/lang/RuntimeException"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLdcInsn(new Float("-1.0"));
+ mv.visitInsn(FRETURN);
+ mv.visitLabel(l23);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+ mv.visitLdcInsn(new Float("42.0"));
+ mv.visitInsn(FRETURN);
+ mv.visitMaxs(4, 10);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(IADD);
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest",
+ "foo",
+ "(I)F",
+ false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(F)V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
new file mode 100644
index 0000000..7866e1b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
@@ -0,0 +1,82 @@
+// 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.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ArrayDimensionGreaterThanSevenTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = ArrayDimensionGreaterThanSevenTest.class;
+ private static final String NAME = CLASS.getCanonicalName();
+
+ private DebugTestConfig getR8CfConfig(String s, Consumer<InternalOptions> optionsConsumer)
+ throws IOException, com.android.tools.r8.CompilationFailedException {
+ Path cfOut = temp.getRoot().toPath().resolve(s);
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+ .setMode(CompilationMode.DEBUG)
+ .setOutput(cfOut, OutputMode.ClassFile)
+ .build(),
+ optionsConsumer);
+ return new CfDebugTestConfig(cfOut);
+ }
+
+ private Stream<DebuggeeState> createStream(DebugTestConfig config) throws Exception {
+ return streamDebugTest(config, NAME, ANDROID_FILTER);
+ }
+
+ @Test
+ @Ignore("b/111296969")
+ // Once R8 does not use expanded frames this can be enabled again.
+ public void test() throws Exception {
+ DebugTestConfig cfConfig = new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DebugTestConfig d8Config = new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ DebugTestConfig r8JarConfig =
+ getR8CfConfig("r8jar.jar", options -> options.enableCfFrontend = false);
+ DebugTestConfig r8CfConfig =
+ getR8CfConfig("r8cf.jar", options -> options.enableCfFrontend = true);
+ new DebugStreamComparator()
+ .add("CF", createStream(cfConfig))
+ .add("R8/CF", createStream(r8CfConfig))
+ .add("R8/Jar", createStream(r8JarConfig))
+ .add("D8", createStream(d8Config))
+ .compare();
+ }
+
+ @Test
+ // Verify that ASM fails when using expanded frames directly.
+ // See b/111296969
+ public void runTestOnAsmDump() throws Exception {
+ Path out = temp.getRoot().toPath().resolve("out.jar");
+ ArchiveConsumer consumer = new ArchiveConsumer(out);
+ consumer.accept(
+ ArrayDimensionGreaterThanSevenTestDump.dump(),
+ DescriptorUtils.javaTypeToDescriptor(NAME),
+ null);
+ consumer.finished(null);
+ ProcessResult result = ToolHelper.runJava(out, NAME);
+ assertEquals("Expected ASM to fail when using visitFrame(F_NEW, ...)", 1, result.exitCode);
+ assertThat(result.stderr, containsString("java.lang.NoClassDefFoundError: F"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index f2e96b3..f010357 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -3,11 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debug;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.google.common.collect.ImmutableList;
@@ -25,45 +27,64 @@
@RunWith(Parameterized.class)
public class DebugInfoWhenInliningTest extends DebugTestBase {
+ public enum Config {
+ CF,
+ DEX_NO_FORCE_JUMBO,
+ DEX_FORCE_JUMBO
+ };
+
private static final String SOURCE_FILE = "Inlining1.java";
private DebugTestConfig makeConfig(
LineNumberOptimization lineNumberOptimization,
- boolean writeProguardMap)
+ boolean writeProguardMap,
+ RuntimeKind runtimeKind)
throws Exception {
- AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+ DebugTestConfig config = null;
Path outdir = temp.newFolder().toPath();
- Path outjar = outdir.resolve("dex_r8_compiled.jar");
- Path proguardMapPath = writeProguardMap ? outdir.resolve("proguard.map") : null;
+ Path outjar = outdir.resolve("r8_compiled.jar");
R8Command.Builder builder =
- R8Command.builder()
- .addProgramFiles(DEBUGGEE_JAR)
- .setMinApiLevel(minSdk.getLevel())
- .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
- .setMode(CompilationMode.RELEASE)
- .setOutput(outjar, OutputMode.DexIndexed);
- if (proguardMapPath != null) {
- builder.setProguardMapOutputPath(proguardMapPath);
+ R8Command.builder().addProgramFiles(DEBUGGEE_JAR).setMode(CompilationMode.RELEASE);
+
+ if (runtimeKind == RuntimeKind.DEX) {
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+ builder
+ .setMinApiLevel(minSdk.getLevel())
+ .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+ .setOutput(outjar, OutputMode.DexIndexed);
+ config = new DexDebugTestConfig(outjar);
+ } else {
+ assert (runtimeKind == RuntimeKind.CF);
+ builder.setOutput(outjar, OutputMode.ClassFile);
+ config = new CfDebugTestConfig(outjar);
}
+
+ if (writeProguardMap) {
+ Path proguardMapPath = outdir.resolve("proguard.map");
+ builder.setProguardMapOutputPath(proguardMapPath);
+ config.setProguardMap(proguardMapPath);
+ }
+
ToolHelper.runR8(
builder.build(), options -> {
options.lineNumberOptimization = lineNumberOptimization;
options.testing.forceJumboStringProcessing = forceJumboStringProcessing;
});
- DebugTestConfig config = new DexDebugTestConfig(outjar);
- config.setProguardMap(proguardMapPath);
+
return config;
}
private boolean forceJumboStringProcessing;
+ private RuntimeKind runtimeKind;
- @Parameters(name="forceJumbo: {0}")
- public static Collection<Boolean> data() {
- return Arrays.asList(true, false);
+ @Parameters(name = "config: {0}")
+ public static Collection<Config> data() {
+ return Arrays.asList(Config.values());
}
- public DebugInfoWhenInliningTest(boolean forceJumboStringProcessing) {
- this.forceJumboStringProcessing = forceJumboStringProcessing;
+ public DebugInfoWhenInliningTest(Config config) {
+ this.forceJumboStringProcessing = config == Config.DEX_FORCE_JUMBO;
+ this.runtimeKind = config == Config.CF ? RuntimeKind.CF : RuntimeKind.DEX;
}
@Test
@@ -75,7 +96,7 @@
// (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
// are emitted duplicated in the dex code, the debugger stops only when there's a change.
int[] lineNumbers = {7, 32, 11, 7};
- testEachLine(makeConfig(LineNumberOptimization.OFF, false), lineNumbers);
+ testEachLine(makeConfig(LineNumberOptimization.OFF, false, runtimeKind), lineNumbers);
}
@Test
@@ -87,13 +108,13 @@
// (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions
// are emitted duplicated in the dex code, the debugger stops only when there's a change.
int[] lineNumbers = {7, 32, 11, 7};
- testEachLine(makeConfig(LineNumberOptimization.OFF, true), lineNumbers);
+ testEachLine(makeConfig(LineNumberOptimization.OFF, true, runtimeKind), lineNumbers);
}
@Test
public void testEachLineOptimized() throws Throwable {
int[] lineNumbers = {1, 2, 3, 4, 5, 6, 7, 8};
- testEachLine(makeConfig(LineNumberOptimization.ON, false), lineNumbers);
+ testEachLine(makeConfig(LineNumberOptimization.ON, false, runtimeKind), lineNumbers);
}
@Test
@@ -130,7 +151,7 @@
new SignatureAndLine("void Inlining2.differentFileMultilevelInliningLevel1()", 36),
new SignatureAndLine("void main(java.lang.String[])", 26)));
testEachLine(
- makeConfig(LineNumberOptimization.ON, true), lineNumbers, inlineFramesList);
+ makeConfig(LineNumberOptimization.ON, true, runtimeKind), lineNumbers, inlineFramesList);
}
private void testEachLine(DebugTestConfig config, int[] lineNumbers) throws Throwable {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
index eb85a7c..3a83e30 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
@@ -214,7 +214,8 @@
try {
if (done) {
assertTrue(
- "Not all streams completed at the same time",
+ "Not all streams completed at the same time. "
+ + "Set 'DebugTestBase.DEBUG_TEST = true' to aid in diagnosing the issue.",
states.stream().allMatch(Objects::isNull));
return;
} else {
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index bda9ac5..6877617 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,11 +10,22 @@
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.graph.invokesuper2.C0;
+import com.android.tools.r8.graph.invokesuper2.C1;
+import com.android.tools.r8.graph.invokesuper2.C2;
+import com.android.tools.r8.graph.invokesuper2.I0;
+import com.android.tools.r8.graph.invokesuper2.I1;
+import com.android.tools.r8.graph.invokesuper2.I2;
+import com.android.tools.r8.graph.invokesuper2.I3;
+import com.android.tools.r8.graph.invokesuper2.I4;
+import com.android.tools.r8.graph.invokesuper2.Main;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
import java.util.Collections;
import org.junit.Test;
@@ -200,6 +211,63 @@
AndroidApp processedApp = processApplication(application);
assertEquals("42", runArt(processedApp));
}
+
+ @Test
+ public void testLookupSuperTarget() throws Exception {
+ String pkg = Main.class.getPackage().getName().replace('.', '/');
+
+ AndroidApp.Builder builder = AndroidApp.builder();
+ for (Class clazz : new Class[]{
+ I0.class, I1.class, I2.class, I3.class, I4.class,
+ C0.class, C1.class, C2.class,
+ Main.class}) {
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+ // At least java.lang.Object is needed as interface method lookup have special handling
+ // of methods on java.lang.Object.
+ builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ }
+ AndroidApp application = builder.build();
+ AppInfo appInfo = getAppInfo(application);
+ DexItemFactory factory = appInfo.dexItemFactory;
+
+ DexType i0 = factory.createType("L" + pkg + "/I0;");
+ DexType i1 = factory.createType("L" + pkg + "/I1;");
+ DexType i2 = factory.createType("L" + pkg + "/I2;");
+ DexType i3 = factory.createType("L" + pkg + "/I3;");
+ DexType i4 = factory.createType("L" + pkg + "/I4;");
+ DexType c0 = factory.createType("L" + pkg + "/C0;");
+ DexType c1 = factory.createType("L" + pkg + "/C1;");
+ DexType c2 = factory.createType("L" + pkg + "/C2;");
+
+ DexProto mProto = factory.createProto(factory.intType);
+ DexString m = factory.createString("m");
+ DexMethod mOnC0 = factory.createMethod(c0, mProto, m);
+ DexMethod mOnC1 = factory.createMethod(c1, mProto, m);
+ DexMethod mOnI0 = factory.createMethod(i0, mProto, m);
+ DexMethod mOnI1 = factory.createMethod(i1, mProto, m);
+ DexMethod mOnI2 = factory.createMethod(i2, mProto, m);
+ DexMethod mOnI3 = factory.createMethod(i3, mProto, m);
+ DexMethod mOnI4 = factory.createMethod(i4, mProto, m);
+
+ assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC0, c1).method);
+ assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI1, c1).method);
+ assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI2, c1).method);
+
+ assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC1, c2).method);
+ assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI3, c2).method);
+ assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI4, c2).method);
+
+ // Copy classes to run on the Java VM.
+ Path out = temp.newFolder().toPath();
+ copyTestClasses(out, I0.class, I1.class, I2.class, I3.class, I4.class);
+ copyTestClasses(out, C0.class, C1.class, C2.class, Main.class);
+ ProcessResult result = ToolHelper.runJava(out, Main.class.getCanonicalName());
+ assertEquals(0, result.exitCode);
+
+ // Process the application and expect the same result on Art.
+ AndroidApp processedApp = processApplication(application);
+ assertEquals(result.stdout, runArt(processedApp, Main.class.getCanonicalName()));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java
new file mode 100644
index 0000000..8e92fb4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java
@@ -0,0 +1,8 @@
+// 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.graph.invokesuper2;
+
+public class C0 implements I0 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java
new file mode 100644
index 0000000..ff98da9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.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.graph.invokesuper2;
+
+public class C1 extends C0 implements I1, I2 {
+ public int m() {
+ // super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/C0.m()I
+ System.out.println(super.m());
+ // I1.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I1.m:()I
+ System.out.println(I1.super.m());
+ // I2.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I2.m:()I
+ System.out.println(I2.super.m());
+ return 3;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java
new file mode 100644
index 0000000..abdf7ff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.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.graph.invokesuper2;
+
+public class C2 extends C0 implements I3, I4 {
+ public int m() {
+ // super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/C0.m()I
+ System.out.println(super.m());
+ // I1.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I3.m:()I
+ System.out.println(I3.super.m());
+ // I2.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I4.m:()I
+ System.out.println(I4.super.m());
+ return 3;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java
new file mode 100644
index 0000000..02b59a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java
@@ -0,0 +1,11 @@
+// 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.graph.invokesuper2;
+
+public interface I0 {
+ default int m() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java
new file mode 100644
index 0000000..0593d90
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java
@@ -0,0 +1,11 @@
+// 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.graph.invokesuper2;
+
+public interface I1 extends I0 {
+ default int m() {
+ return 1;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java
new file mode 100644
index 0000000..22956c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java
@@ -0,0 +1,11 @@
+// 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.graph.invokesuper2;
+
+public interface I2 extends I0 {
+ default int m() {
+ return 2;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java
new file mode 100644
index 0000000..e64154e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java
@@ -0,0 +1,8 @@
+// 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.graph.invokesuper2;
+
+public interface I3 extends I1 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java
new file mode 100644
index 0000000..ddc5ad0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java
@@ -0,0 +1,8 @@
+// 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.graph.invokesuper2;
+
+public interface I4 extends I2 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java
new file mode 100644
index 0000000..6a2a3b9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java
@@ -0,0 +1,13 @@
+// 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.graph.invokesuper2;
+
+public class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new C1().m());
+ System.out.println(new C2().m());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 3239191..e7a8285 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.ir.optimize.classinliner.code.C;
import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.lambdas.LambdasTestClass;
import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -301,6 +302,37 @@
assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
}
+ @Test
+ public void testDesugaredLambdas() throws Exception {
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(LambdasTestClass.class),
+ ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
+ ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+ };
+ AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+
+ String javaOutput = runOnJava(LambdasTestClass.class);
+ String artOutput = runOnArt(app, LambdasTestClass.class);
+ assertEquals(javaOutput, artOutput);
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder"),
+ collectTypes(clazz, "testStatelessLambda", "void"));
+
+ assertEquals(
+ Sets.newHashSet(
+ "java.lang.StringBuilder"),
+ collectTypes(clazz, "testStatefulLambda", "void", "java.lang.String", "java.lang.String"));
+
+ assertEquals(0,
+ inspector.allClasses().stream()
+ .filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
+ }
+
private Set<String> collectTypes(
ClassSubject clazz, String methodName, String retValue, String... params) {
return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
new file mode 100644
index 0000000..130f895
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -0,0 +1,54 @@
+// 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.ir.optimize.classinliner.lambdas;
+
+public class LambdasTestClass {
+ private static int ID = 0;
+
+ private static int nextInt() {
+ return ID++;
+ }
+
+ private static String next() {
+ return Integer.toString(nextInt());
+ }
+
+ public interface Iface {
+ String foo();
+ }
+
+ public static class IfaceUtil {
+ public static void act(Iface iface) {
+ System.out.println("" + next() + "> " + iface.foo());
+ }
+ }
+
+ public static void main(String[] args) {
+ LambdasTestClass test = new LambdasTestClass();
+ test.testStatelessLambda();
+ test.testStatefulLambda(next(), next());
+ }
+
+ public static String exact() {
+ return next();
+ }
+
+ public static String almost(String... s) {
+ return next();
+ }
+
+ private synchronized void testStatelessLambda() {
+ IfaceUtil.act(() -> next());
+ IfaceUtil.act(LambdasTestClass::next);
+ IfaceUtil.act(LambdasTestClass::exact);
+ IfaceUtil.act(LambdasTestClass::almost);
+ }
+
+ private synchronized void testStatefulLambda(String a, String b) {
+ IfaceUtil.act(() -> a);
+ IfaceUtil.act(() -> a + b);
+ IfaceUtil.act((a + b)::toLowerCase);
+ }
+}
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 82381dd..9c15ce7 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -353,12 +353,16 @@
return out.toByteArray();
}
- public List<byte[]> buildClasses() throws Exception {
- List<byte[]> result = new ArrayList<>();
+ public ImmutableList.Builder<byte[]> buildClasses(ImmutableList.Builder<byte[]> builder)
+ throws Exception {
for (ClassBuilder clazz : classes) {
- result.add(compile(clazz));
+ builder.add(compile(clazz));
}
- return result;
+ return builder;
+ }
+
+ public List<byte[]> buildClasses() throws Exception {
+ return buildClasses(ImmutableList.builder()).build();
}
public AndroidApp build() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
new file mode 100644
index 0000000..90fba5f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
@@ -0,0 +1,342 @@
+// 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.regress.b111250398;
+
+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.code.Iget;
+import com.android.tools.r8.code.IgetObject;
+import com.android.tools.r8.code.Sget;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexField;
+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.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import org.junit.Test;
+
+// Copy of javax.inject.Provider.
+interface Provider<T> {
+ T get();
+}
+
+// Copy of dagger.internal.SingleClass.
+final class SingleCheck<T> implements Provider<T> {
+ private static final Object UNINITIALIZED = new Object();
+
+ private volatile Provider<T> provider;
+ private volatile Object instance = UNINITIALIZED;
+
+ private SingleCheck(Provider<T> provider) {
+ assert provider != null;
+ this.provider = provider;
+ }
+
+ @SuppressWarnings("unchecked") // cast only happens when result comes from the delegate provider
+ @Override
+ public T get() {
+ Object local = instance;
+ if (local == UNINITIALIZED) {
+ // provider is volatile and might become null after the check, so retrieve the provider first
+ Provider<T> providerReference = provider;
+ if (providerReference == null) {
+ // The provider was null, so the instance must already be set
+ local = instance;
+ } else {
+ local = providerReference.get();
+ instance = local;
+
+ // Null out the reference to the provider. We are never going to need it again, so we can
+ // make it eligible for GC.
+ provider = null;
+ }
+ }
+ return (T) local;
+ }
+
+ // This method is not relevant for the test.
+ /*
+ public static <P extends Provider<T>, T> Provider<T> provider(P provider) {
+ // If a scoped @Binds delegates to a scoped binding, don't cache the value again.
+ if (provider instanceof SingleCheck || provider instanceof DoubleCheck) {
+ return provider;
+ }
+ return new SingleCheck<T>(checkNotNull(provider));
+ }
+ */
+}
+
+// Several field gets on non-volatile and volatile fields on the same class.
+class A {
+ int t;
+ int f;
+ static int sf;
+ volatile int v;
+ static volatile int sv;
+
+ public void mf() {
+ t = f;
+ t = f;
+ t = f;
+ t = f;
+ t = f;
+ }
+
+ public void msf() {
+ t = sf;
+ t = sf;
+ t = sf;
+ t = sf;
+ t = sf;
+ }
+
+ public void mv() {
+ t = v;
+ t = v;
+ t = v;
+ t = v;
+ t = v;
+ }
+
+ public void msv() {
+ t = sv;
+ t = sv;
+ t = sv;
+ t = sv;
+ t = sv;
+ }
+}
+
+// Several field gets on non-volatile and volatile fields on different class.
+class B {
+ int t;
+
+ public void mf(A a) {
+ t = a.f;
+ t = a.f;
+ t = a.f;
+ t = a.f;
+ t = a.f;
+ }
+
+ public void msf() {
+ t = A.sf;
+ t = A.sf;
+ t = A.sf;
+ t = A.sf;
+ t = A.sf;
+ }
+
+ public void mv(A a) {
+ t = a.v;
+ t = a.v;
+ t = a.v;
+ t = a.v;
+ t = a.v;
+ }
+
+ public void msv() {
+ t = A.sv;
+ t = A.sv;
+ t = A.sv;
+ t = A.sv;
+ t = A.sv;
+ }
+}
+
+// Modified sample from http://tutorials.jenkov.com/java-concurrency/volatile.html.
+class C {
+ private int years;
+ private int months;
+ private volatile int days;
+
+ public int totalDays() {
+ int total = this.days;
+ total += months * 30;
+ total += years * 365;
+ return total;
+ }
+
+ public int totalDaysTimes2() {
+ int total = this.days;
+ total += months * 30;
+ total += years * 365;
+ total += this.days;
+ total += months * 30;
+ total += years * 365;
+ return total;
+ }
+
+ public int totalDaysTimes3() {
+ int total = this.days;
+ total += months * 30;
+ total += years * 365;
+ total += this.days;
+ total += months * 30;
+ total += years * 365;
+ total += this.days;
+ total += months * 30;
+ total += years * 365;
+ return total;
+ }
+
+ public void update(int years, int months, int days){
+ this.years = years;
+ this.months = months;
+ this.days = days;
+ }
+}
+
+public class B111250398 extends TestBase {
+
+ private void releaseMode(InternalOptions options) {
+ options.debug = false;
+ }
+
+ private long countIget(DexCode code, DexField field) {
+ return Arrays.stream(code.instructions)
+ .filter(instruction -> instruction instanceof Iget)
+ .map(instruction -> (Iget) instruction)
+ .filter(get -> get.getField() == field)
+ .count();
+ }
+
+ private long countSget(DexCode code, DexField field) {
+ return Arrays.stream(code.instructions)
+ .filter(instruction -> instruction instanceof Sget)
+ .map(instruction -> (Sget) instruction)
+ .filter(get -> get.getField() == field)
+ .count();
+ }
+
+ private long countIgetObject(MethodSubject method, FieldSubject field) {
+ return Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
+ .filter(instruction -> instruction instanceof IgetObject)
+ .map(instruction -> (IgetObject) instruction)
+ .filter(get -> get.getField() == field.getField().field)
+ .count();
+ }
+
+ private void check(DexInspector inspector, int mfOnBGets, int msfOnBGets) {
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+ MethodSubject mfOnA = classA.method("void", "mf", ImmutableList.of());
+ assertThat(mfOnA, isPresent());
+ MethodSubject msfOnA = classA.method("void", "msf", ImmutableList.of());
+ assertThat(msfOnA, isPresent());
+ MethodSubject mvOnA = classA.method("void", "mv", ImmutableList.of());
+ assertThat(mvOnA, isPresent());
+ MethodSubject msvOnA = classA.method("void", "msv", ImmutableList.of());
+ assertThat(msvOnA, isPresent());
+ FieldSubject fOnA = classA.field("int", "f");
+ assertThat(fOnA, isPresent());
+ FieldSubject sfOnA = classA.field("int", "sf");
+ assertThat(sfOnA, isPresent());
+ FieldSubject vOnA = classA.field("int", "v");
+ assertThat(vOnA, isPresent());
+ FieldSubject svOnA = classA.field("int", "sv");
+ assertThat(svOnA, isPresent());
+ ClassSubject classB = inspector.clazz(B.class);
+ assertThat(classB, isPresent());
+ MethodSubject mfOnB = classB.method("void", "mf", ImmutableList.of(classA.getOriginalName()));
+ assertThat(mfOnB, isPresent());
+ MethodSubject msfOnB = classB.method("void", "msf", ImmutableList.of());
+ assertThat(msfOnB, isPresent());
+ MethodSubject mvOnB = classB.method("void", "mv", ImmutableList.of(classA.getOriginalName()));
+ assertThat(mvOnB, isPresent());
+ MethodSubject msvOnB = classB.method("void", "msv", ImmutableList.of());
+ assertThat(msvOnB, isPresent());
+ // Field load of volatile fields are never eliminated.
+ assertEquals(5, countIget(mvOnA.getMethod().getCode().asDexCode(), vOnA.getField().field));
+ assertEquals(5, countSget(msvOnA.getMethod().getCode().asDexCode(), svOnA.getField().field));
+ assertEquals(5, countIget(mvOnB.getMethod().getCode().asDexCode(), vOnA.getField().field));
+ assertEquals(5, countSget(msvOnB.getMethod().getCode().asDexCode(), svOnA.getField().field));
+ // For fields on the same class both separate compilation (D8) and whole program
+ // compilation (R8) will eliminate field loads on non-volatile fields.
+ assertEquals(1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+ assertEquals(1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+ // For fields on other class both separate compilation (D8) and whole program
+ // compilation (R8) will differ in the eliminated field loads of non-volatile fields.
+ assertEquals(mfOnBGets,
+ countIget(mfOnB.getMethod().getCode().asDexCode(), fOnA.getField().field));
+ assertEquals(msfOnBGets,
+ countSget(msfOnB.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+ }
+
+ @Test
+ public void testSeparateCompilation() throws Exception {
+ DexInspector inspector =
+ new DexInspector(compileWithD8(readClasses(A.class, B.class), this::releaseMode));
+ check(inspector, 5, 5);
+ }
+
+ @Test
+ public void testWholeProgram() throws Exception {
+ DexInspector inspector =
+ new DexInspector(compileWithR8(readClasses(A.class, B.class), this::releaseMode));
+ // The reason for getting two Igets in B.mf is that the first Iget inserts a NonNull
+ // instruction which creates a new value for the remaining Igets.
+ check(inspector, 2, 1);
+ }
+
+ private void checkMixed(AndroidApp app) throws Exception{
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject classC = inspector.clazz(C.class);
+ assertThat(classC, isPresent());
+ MethodSubject totalDays = classC.method("int", "totalDays", ImmutableList.of());
+ assertThat(totalDays, isPresent());
+ MethodSubject totalDaysTimes2 = classC.method("int", "totalDaysTimes2", ImmutableList.of());
+ assertThat(totalDaysTimes2, isPresent());
+ MethodSubject totalDaysTimes3 = classC.method("int", "totalDaysTimes3", ImmutableList.of());
+ assertThat(totalDaysTimes3, isPresent());
+ FieldSubject years = classC.field("int", "years");
+ assertThat(years, isPresent());
+ FieldSubject months = classC.field("int", "months");
+ assertThat(months, isPresent());
+ FieldSubject days = classC.field("int", "days");
+ assertThat(days, isPresent());
+
+
+ for (FieldSubject field : new FieldSubject[]{years, months, days}) {
+ assertEquals(1,
+ countIget(totalDays.getMethod().getCode().asDexCode(), field.getField().field));
+ assertEquals(2,
+ countIget(totalDaysTimes2.getMethod().getCode().asDexCode(), field.getField().field));
+ assertEquals(3,
+ countIget(totalDaysTimes3.getMethod().getCode().asDexCode(), field.getField().field));
+ }
+ }
+
+ @Test
+ public void testMixedVolatileNonVolatile() throws Exception {
+ AndroidApp app = readClasses(C.class);
+ checkMixed(compileWithD8(app, this::releaseMode));
+ checkMixed(compileWithR8(app, this::releaseMode));
+ }
+
+ private void checkDaggerSingleProviderGet(AndroidApp app) throws Exception {
+ DexInspector inspector = new DexInspector(app);
+ MethodSubject get =
+ inspector.clazz(SingleCheck.class).method("java.lang.Object", "get", ImmutableList.of());
+ assertThat(get, isPresent());
+ FieldSubject instance =
+ inspector.clazz(SingleCheck.class).field("java.lang.Object", "instance");
+ assertEquals(2, countIgetObject(get, instance));
+ }
+
+ @Test
+ public void testDaggerSingleProvider() throws Exception {
+ AndroidApp app = readClasses(Provider.class, SingleCheck.class);
+ checkDaggerSingleProviderGet(compileWithD8(app, this::releaseMode));
+ checkDaggerSingleProviderGet(compileWithR8(app, this::releaseMode));
+ }
+}
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 eba3be8..955d1ee 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -423,6 +423,8 @@
public abstract boolean isAnonymousClass();
+ public abstract boolean isSynthesizedJavaLambdaClass();
+
public abstract String getOriginalSignatureAttribute();
public abstract String getFinalSignatureAttribute();
@@ -514,6 +516,11 @@
}
@Override
+ public boolean isSynthesizedJavaLambdaClass() {
+ return false;
+ }
+
+ @Override
public String getOriginalSignatureAttribute() {
return null;
}
@@ -709,6 +716,11 @@
}
@Override
+ public boolean isSynthesizedJavaLambdaClass() {
+ return dexClass.type.getName().contains("$Lambda$");
+ }
+
+ @Override
public String getOriginalSignatureAttribute() {
return DexInspector.this.getOriginalSignatureAttribute(
dexClass.annotations, GenericSignatureParser::parseClassSignature);
diff --git a/tools/proguard.py b/tools/proguard.py
index efcf560..640094f 100755
--- a/tools/proguard.py
+++ b/tools/proguard.py
@@ -15,14 +15,14 @@
PROGUARD_JAR = os.path.join(utils.REPO_ROOT, 'third_party', 'proguard',
'proguard_internal_159423826', 'ProGuard_deploy.jar')
-def run(args, track_memory_file = None):
+def run(args, track_memory_file = None, stdout=None, stderr=None):
cmd = []
if track_memory_file:
cmd.extend(['tools/track_memory.sh', track_memory_file])
cmd.extend(['java', '-jar', PROGUARD_JAR])
cmd.extend(args)
utils.PrintCmd(cmd)
- subprocess.check_call(cmd)
+ subprocess.call(cmd, stdout=stdout, stderr=stderr)
def Main():
run(sys.argv[1:])
diff --git a/tools/run_proguard_dx_on_app.py b/tools/run_proguard_dx_on_app.py
index a953229..4624f26 100755
--- a/tools/run_proguard_dx_on_app.py
+++ b/tools/run_proguard_dx_on_app.py
@@ -115,7 +115,10 @@
track_memory_file = None
if options.print_memoryuse:
track_memory_file = join(temp, utils.MEMORY_USE_TMP_FILE)
- proguard.run(args, track_memory_file = track_memory_file)
+ proguard.run(
+ args,
+ track_memory_file = track_memory_file,
+ stdout=open(os.devnull, 'w'))
if options.print_memoryuse:
proguard_memoryuse = utils.grep_memoryuse(track_memory_file)