Merge "Test emitting stack-map frames with array types of more than 7 dimensions."
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 69a744f..55c4134 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -9,6 +9,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -125,6 +126,12 @@
if (resolutionResult.asListOfTargets().isEmpty()) {
return null;
}
+ // According to
+ // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokespecial, use
+ // the "symbolic reference" if the "symbolic reference" does not name a class.
+ if (definitionFor(method.holder).isInterface()) {
+ return resolveMethodOnInterface(method.holder, method).asSingleTarget();
+ }
// Then, resume on the search, but this time, starting from the holder of the caller.
DexClass contextClass = definitionFor(invocationContext);
if (contextClass == null || contextClass.superType == null) {
@@ -536,14 +543,14 @@
private static class MultiResultBuilder {
- private ImmutableList.Builder<DexEncodedMethod> builder;
+ private ImmutableSet.Builder<DexEncodedMethod> builder;
private DexEncodedMethod singleResult;
void add(DexEncodedMethod result) {
if (builder != null) {
builder.add(result);
- } else if (singleResult != null) {
- builder = ImmutableList.builder();
+ } else if (singleResult != null && !singleResult.equals(result)) {
+ builder = ImmutableSet.builder();
builder.add(singleResult, result);
singleResult = null;
} else {
@@ -553,7 +560,7 @@
ResolutionResult build() {
if (builder != null) {
- return new MultiResult(builder.build());
+ return new MultiResult(builder.build().asList());
} else {
return singleResult;
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index a8bda9e..ce361e8 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -169,6 +169,10 @@
return super.lookupSuperTarget(method, invocationContext);
}
+ protected boolean hasAnyInstantiatedLambdas(DexType type) {
+ return true; // Don't know, there might be.
+ }
+
// For mapping invoke interface instruction to target methods.
public Set<DexEncodedMethod> lookupInterfaceTargets(DexMethod method) {
// First check that there is a target for this invoke-interface to hit. If there is none,
@@ -177,11 +181,38 @@
if (topTarget.asResultOfResolve() == null) {
return null;
}
- Set<DexType> set = subtypes(method.holder);
- if (set.isEmpty()) {
- return Collections.emptySet();
- }
+
Set<DexEncodedMethod> result = new HashSet<>();
+ if (topTarget.hasSingleTarget()) {
+ // Add default interface methods to the list of targets.
+ //
+ // This helps to make sure we take into account synthesized lambda classes
+ // that we are not aware of. Like in the following example, we know that all
+ // classes, XX in this case, override B::bar(), but there are also synthesized
+ // classes for lambda which don't, so we still need default method to be live.
+ //
+ // public static void main(String[] args) {
+ // X x = () -> {};
+ // x.bar();
+ // }
+ //
+ // interface X {
+ // void foo();
+ // default void bar() { }
+ // }
+ //
+ // class XX implements X {
+ // public void foo() { }
+ // public void bar() { }
+ // }
+ //
+ DexEncodedMethod singleTarget = topTarget.asSingleTarget();
+ if (singleTarget.getCode() != null && hasAnyInstantiatedLambdas(singleTarget.method.holder)) {
+ result.add(singleTarget);
+ }
+ }
+
+ Set<DexType> set = subtypes(method.holder);
for (DexType type : set) {
DexClass clazz = definitionFor(type);
// Default methods are looked up when looking at a specific subtype that does not
@@ -212,8 +243,7 @@
*/
public Set<DexEncodedMethod> lookupLambdaImplementedMethods(
DexCallSite callSite, Reporter reporter) {
- List<DexType> callSiteInterfaces =
- LambdaDescriptor.getInterfaces(callSite, this, dexItemFactory);
+ List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, this);
if (callSiteInterfaces == null) {
if (!isStringConcat(callSite.bootstrapMethod)) {
Diagnostic message =
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 49145fa..f8608d6 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -86,6 +86,10 @@
public void registerCallSite(DexCallSite callSite) {
registerMethodHandle(callSite.bootstrapMethod);
+ // Lambda metafactory will use this type as the main SAM
+ // interface for the dynamically created lambda class.
+ registerTypeReference(callSite.methodProto.returnType);
+
// Register bootstrap method arguments.
// Only Type, MethodHandle, and MethodType need to be registered.
for (DexValue arg : callSite.bootstrapArgs) {
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..8db07f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -0,0 +1,75 @@
+// 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. initialCapacity will be
+ * passed to the HashMap constructor.
+ */
+ public CanonicalPositions(
+ Position callerPosition, boolean preserveCaller, int initialCapacity, DexMethod method) {
+ canonicalPositions = new HashMap<>(initialCapacity);
+ 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/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index b59794b..e0c47da 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,
+ 1 + (callerPosition == null ? 0 : 1) + (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 c6ba97d..63843ee 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
@@ -739,7 +739,7 @@
assert options.enableInlining && inliner != null;
TypeEnvironment effectivelyFinalTypeEnvironment = typeEnvironment;
classInliner.processMethodCode(
- appInfo.withLiveness(), method, code, isProcessedConcurrently,
+ appInfo.withLiveness(), codeRewriter, method, code, isProcessedConcurrently,
methodsToInline -> inliner.performForcedInlining(method, code, methodsToInline),
Suppliers.memoize(() -> inliner.createDefaultOracle(
method, code, effectivelyFinalTypeEnvironment,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 94bf56c..9d16497 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -37,7 +37,7 @@
final DexString name;
final DexProto erasedProto;
final DexProto enforcedProto;
- final DexMethodHandle implHandle;
+ public final DexMethodHandle implHandle;
final List<DexType> interfaces = new ArrayList<>();
final Set<DexProto> bridges = Sets.newIdentityHashSet();
@@ -173,7 +173,6 @@
return false;
}
-
boolean staticTarget = implHandle.type.isInvokeStatic();
boolean instanceTarget = implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect();
boolean initTarget = implHandle.type.isInvokeConstructor();
@@ -222,13 +221,23 @@
* Matches call site for lambda metafactory invocation pattern and
* returns extracted match information, or null if match failed.
*/
- static LambdaDescriptor infer(DexCallSite callSite, AppInfo appInfo, DexItemFactory factory) {
+ public static LambdaDescriptor tryInfer(DexCallSite callSite, AppInfo appInfo) {
+ LambdaDescriptor descriptor = infer(callSite, appInfo);
+ return descriptor == MATCH_FAILED ? null : descriptor;
+ }
+
+ /**
+ * Matches call site for lambda metafactory invocation pattern and
+ * returns extracted match information, or MATCH_FAILED if match failed.
+ */
+ static LambdaDescriptor infer(DexCallSite callSite, AppInfo appInfo) {
// We expect bootstrap method to be either `metafactory` or `altMetafactory` method
// of `java.lang.invoke.LambdaMetafactory` class. Both methods are static.
if (!callSite.bootstrapMethod.type.isInvokeStatic()) {
return LambdaDescriptor.MATCH_FAILED;
}
+ DexItemFactory factory = appInfo.dexItemFactory;
DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
boolean isMetafactoryMethod = bootstrapMethod == factory.metafactoryMethod;
boolean isAltMetafactoryMethod = bootstrapMethod == factory.metafactoryAltMethod;
@@ -335,13 +344,11 @@
}
}
- public static List<DexType> getInterfaces(
- DexCallSite callSite, AppInfo appInfo, DexItemFactory factory) {
- LambdaDescriptor descriptor = infer(callSite, appInfo, factory);
+ public static List<DexType> getInterfaces(DexCallSite callSite, AppInfo appInfo) {
+ LambdaDescriptor descriptor = infer(callSite, appInfo);
if (descriptor == LambdaDescriptor.MATCH_FAILED) {
return null;
}
- assert descriptor.interfaces != null;
return descriptor.interfaces;
}
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 d6b99a7..e7686db 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
@@ -194,7 +194,7 @@
: putIfAbsent(
knownCallSites,
callSite,
- LambdaDescriptor.infer(callSite, this.converter.appInfo, this.factory));
+ LambdaDescriptor.infer(callSite, this.converter.appInfo));
}
private boolean isInMainDexList(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 121f47d..94e8147 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -2362,7 +2362,7 @@
&& (theIf.getType() == Type.EQ || theIf.getType() == Type.NE)) {
if (inValues.get(0).isNeverNull()) {
simplifyIfWithKnownCondition(code, block, theIf, 1);
- } else {
+ } else if (typeEnvironment != null) {
// TODO(b/72693244): annotate type lattice to value
TypeLatticeElement l = typeEnvironment.getLatticeElement(inValues.get(0));
if (!l.isPrimitive() && !l.isNullable()) {
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 219b5b1..5bf2c74 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
@@ -12,6 +12,7 @@
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.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
import com.android.tools.r8.ir.optimize.InliningOracle;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -117,6 +118,7 @@
//
public final void processMethodCode(
AppInfoWithLiveness appInfo,
+ CodeRewriter codeRewriter,
DexEncodedMethod method,
IRCode code,
Predicate<DexEncodedMethod> isProcessedConcurrently,
@@ -164,11 +166,14 @@
}
// Inline the class instance.
- processor.processInlining(code, inliner);
+ boolean anyInlinedMethods = processor.processInlining(code, inliner);
// Restore normality.
code.removeAllTrivialPhis();
assert code.isConsistentSSA();
+ if (anyInlinedMethods) {
+ codeRewriter.simplifyIf(code, null);
+ }
rootsIterator.remove();
repeat = true;
}
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 b3bde90..57ce42f 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
@@ -271,14 +271,16 @@
// * remove field writes
// * remove root instruction
//
- void processInlining(IRCode code, InlinerAction inliner) {
+ // Returns `true` if at least one method was inlined.
+ boolean processInlining(IRCode code, InlinerAction inliner) {
replaceUsagesAsUnusedArgument(code);
- forceInlineExtraMethodInvocations(inliner);
- forceInlineDirectMethodInvocations(inliner);
+ boolean anyInlinedMethods = forceInlineExtraMethodInvocations(inliner);
+ anyInlinedMethods |= forceInlineDirectMethodInvocations(inliner);
removeMiscUsages(code);
removeFieldReads(code);
removeFieldWrites();
removeInstruction(root);
+ return anyInlinedMethods;
}
private void replaceUsagesAsUnusedArgument(IRCode code) {
@@ -298,9 +300,9 @@
unusedArguments.clear();
}
- private void forceInlineExtraMethodInvocations(InlinerAction inliner) {
+ private boolean forceInlineExtraMethodInvocations(InlinerAction inliner) {
if (extraMethodCalls.isEmpty()) {
- return;
+ return false;
}
// Inline extra methods.
@@ -320,12 +322,15 @@
}
assert extraMethodCalls.isEmpty();
assert unusedArguments.isEmpty();
+ return true;
}
- private void forceInlineDirectMethodInvocations(InlinerAction inliner) {
- if (!methodCallsOnInstance.isEmpty()) {
- inliner.inline(methodCallsOnInstance);
+ private boolean forceInlineDirectMethodInvocations(InlinerAction inliner) {
+ if (methodCallsOnInstance.isEmpty()) {
+ return false;
}
+ inliner.inline(methodCallsOnInstance);
+ return true;
}
// Remove miscellaneous users before handling field reads.
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 8335cba..6a12b60 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -39,6 +39,7 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
@@ -65,6 +66,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -153,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.
@@ -296,6 +300,10 @@
@Override
public boolean registerInvokeVirtual(DexMethod method) {
+ return registerInvokeVirtual(method, KeepReason.invokedFrom(currentMethod));
+ }
+
+ boolean registerInvokeVirtual(DexMethod method, KeepReason keepReason) {
if (appInfo.dexItemFactory.classMethods.isReflectiveMemberLookup(method)) {
if (forceProguardCompatibility) {
// TODO(b/76181966): whether or not add this rule in normal mode.
@@ -311,24 +319,32 @@
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeVirtual `%s`.", method);
}
- workList.add(Action.markReachableVirtual(method, KeepReason.invokedFrom(currentMethod)));
+ workList.add(Action.markReachableVirtual(method, keepReason));
return true;
}
@Override
public boolean registerInvokeDirect(DexMethod method) {
+ return registerInvokeDirect(method, KeepReason.invokedFrom(currentMethod));
+ }
+
+ boolean registerInvokeDirect(DexMethod method, KeepReason keepReason) {
if (!registerItemWithTarget(directInvokes, method)) {
return false;
}
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeDirect `%s`.", method);
}
- handleInvokeOfDirectTarget(method, KeepReason.invokedFrom(currentMethod));
+ handleInvokeOfDirectTarget(method, keepReason);
return true;
}
@Override
public boolean registerInvokeStatic(DexMethod method) {
+ return registerInvokeStatic(method, KeepReason.invokedFrom(currentMethod));
+ }
+
+ boolean registerInvokeStatic(DexMethod method, KeepReason keepReason) {
if (method == appInfo.dexItemFactory.classMethods.forName
|| appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method)) {
if (forceProguardCompatibility) {
@@ -345,19 +361,23 @@
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeStatic `%s`.", method);
}
- handleInvokeOfStaticTarget(method, KeepReason.invokedFrom(currentMethod));
+ handleInvokeOfStaticTarget(method, keepReason);
return true;
}
@Override
public boolean registerInvokeInterface(DexMethod method) {
+ return registerInvokeInterface(method, KeepReason.invokedFrom(currentMethod));
+ }
+
+ boolean registerInvokeInterface(DexMethod method, KeepReason keepReason) {
if (!registerItemWithTarget(interfaceInvokes, method)) {
return false;
}
if (Log.ENABLED) {
Log.verbose(getClass(), "Register invokeInterface `%s`.", method);
}
- workList.add(Action.markReachableInterface(method, KeepReason.invokedFrom(currentMethod)));
+ workList.add(Action.markReachableInterface(method, keepReason));
return true;
}
@@ -403,7 +423,11 @@
@Override
public boolean registerNewInstance(DexType type) {
- markInstantiated(type, currentMethod);
+ return registerNewInstance(type, KeepReason.instantiatedIn(currentMethod));
+ }
+
+ public boolean registerNewInstance(DexType type, KeepReason keepReason) {
+ markInstantiated(type, keepReason);
return true;
}
@@ -459,6 +483,88 @@
appInfo.lookupLambdaImplementedMethods(callSite, options.reporter)) {
markLambdaInstantiated(method.method.holder, currentMethod);
}
+
+ LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo);
+ if (descriptor == null) {
+ return;
+ }
+
+ // For call sites representing a lambda, we link the targeted method
+ // or field as if it were referenced from the current method.
+
+ DexMethodHandle implHandle = descriptor.implHandle;
+ assert implHandle != null;
+ switch (implHandle.type) {
+ case INVOKE_STATIC:
+ registerInvokeStatic(implHandle.asMethod(),
+ KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ break;
+ case INVOKE_INTERFACE:
+ registerInvokeInterface(implHandle.asMethod(),
+ KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ break;
+ case INVOKE_INSTANCE:
+ registerInvokeVirtual(implHandle.asMethod(),
+ KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ break;
+ case INVOKE_DIRECT:
+ registerInvokeDirect(implHandle.asMethod(),
+ KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ break;
+ case INVOKE_CONSTRUCTOR:
+ registerNewInstance(implHandle.asMethod().holder,
+ KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ break;
+ default:
+ throw new Unreachable();
+ }
+
+ // In similar way as what transitionMethodsForInstantiatedClass does for existing
+ // classes we need to process classes dynamically created by runtime for lambdas.
+ // We make an assumption that such classes are inherited directly from java.lang.Object
+ // and implement all lambda interfaces.
+
+ ScopedDexMethodSet seen = new ScopedDexMethodSet();
+ List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
+ if (directInterfaces == null) {
+ return;
+ }
+
+ Set<DexType> allInterfaces = Sets.newHashSet(directInterfaces);
+ DexType instantiatedType = appInfo.dexItemFactory.objectType;
+ DexClass clazz = appInfo.definitionFor(instantiatedType);
+ if (clazz == null) {
+ reportMissingClass(instantiatedType);
+ return;
+ }
+
+ // We only have to look at virtual methods here, as only those can actually be executed at
+ // runtime. Illegal dispatch situations and the corresponding exceptions are already handled
+ // by the reachability logic.
+ SetWithReason<DexEncodedMethod> reachableMethods =
+ reachableVirtualMethods.get(instantiatedType);
+ if (reachableMethods != null) {
+ transitionNonAbstractMethodsToLiveAndShadow(
+ reachableMethods.getItems(), instantiatedType, seen);
+ }
+ Collections.addAll(allInterfaces, clazz.interfaces.values);
+
+ // The set now contains all virtual methods on the type and its supertype that are reachable.
+ // In a second step, we now look at interfaces. We have to do this in this order due to JVM
+ // semantics for default methods. A default method is only reachable if it is not overridden
+ // in any superclass. Also, it is not defined which default method is chosen if multiple
+ // interfaces define the same default method. Hence, for every interface (direct or indirect),
+ // we have to look at the interface chain and mark default methods as reachable, not taking
+ // the shadowing of other interface chains into account.
+ // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
+ for (DexType iface : allInterfaces) {
+ DexClass ifaceClazz = appInfo.definitionFor(iface);
+ if (ifaceClazz == null) {
+ reportMissingClass(iface);
+ return;
+ }
+ transitionDefaultMethodsForInstantiatedClass(iface, instantiatedType, seen);
+ }
}
private boolean registerConstClassOrCheckCast(DexType type) {
@@ -483,9 +589,13 @@
}
private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
+ DexClass methodHolderClass = appInfo.definitionFor(method.getHolder());
+ if (methodHolderClass != null && methodHolderClass.isInterface()) {
+ return method;
+ }
DexClass holderClass = appInfo.definitionFor(currentMethod.method.getHolder());
- if (holderClass == null || holderClass.superType == null) {
- // We do not know better.
+ if (holderClass == null || holderClass.superType == null || holderClass.isInterface()) {
+ // We do not know better or this call is made from an interface.
return method;
}
// Return the invoked method on the supertype.
@@ -601,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);
+ }
}
}
@@ -836,7 +953,7 @@
enqueueRootItems(rootSet.getDependentItems(field));
}
- private void markInstantiated(DexType type, DexEncodedMethod method) {
+ private void markInstantiated(DexType type, KeepReason keepReason) {
if (instantiatedTypes.contains(type)) {
return;
}
@@ -848,7 +965,7 @@
if (Log.ENABLED) {
Log.verbose(getClass(), "Register new instantiation of `%s`.", clazz);
}
- workList.add(Action.markInstantiated(clazz, KeepReason.instantiatedIn(method)));
+ workList.add(Action.markInstantiated(clazz, keepReason));
}
private void markLambdaInstantiated(DexType itf, DexEncodedMethod method) {
@@ -1040,17 +1157,17 @@
if (target.accessFlags.isPrivate()) {
brokenSuperInvokes.add(method);
}
- assert !superInvokeDependencies.containsKey(from) || !superInvokeDependencies.get(from)
- .contains(target);
if (Log.ENABLED) {
Log.verbose(getClass(), "Adding super constraint from `%s` to `%s`", from.method,
target.method);
}
- superInvokeDependencies.computeIfAbsent(from, ignore -> Sets.newIdentityHashSet()).add(target);
- if (liveMethods.contains(from)) {
- markMethodAsTargeted(target, KeepReason.invokedViaSuperFrom(from));
- if (!target.accessFlags.isAbstract()) {
- markVirtualMethodAsLive(target, KeepReason.invokedViaSuperFrom(from));
+ if (superInvokeDependencies.computeIfAbsent(
+ from, ignore -> Sets.newIdentityHashSet()).add(target)) {
+ if (liveMethods.contains(from)) {
+ markMethodAsTargeted(target, KeepReason.invokedViaSuperFrom(from));
+ if (!target.accessFlags.isAbstract()) {
+ markVirtualMethodAsLive(target, KeepReason.invokedViaSuperFrom(from));
+ }
}
}
}
@@ -1479,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.
@@ -1596,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();
@@ -1636,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;
@@ -1675,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 =
@@ -1726,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;
@@ -1837,6 +1963,11 @@
return builder.build();
}
+ @Override
+ protected boolean hasAnyInstantiatedLambdas(DexType type) {
+ return instantiatedLambdas.contains(type);
+ }
+
private static <T extends PresortedComparable<T>> ImmutableSortedSet<T> rewriteItems(
Set<T> original, Function<T, T> rewrite) {
ImmutableSortedSet.Builder<T> builder =
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 78ef9e9..97b14b5 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -35,6 +35,10 @@
return new InvokedFrom(method);
}
+ public static KeepReason invokedFromLambdaCreatedIn(DexEncodedMethod method) {
+ return new InvokedFromLambdaCreatedIn(method);
+ }
+
public static KeepReason isLibraryMethod() {
return new IsLibraryMethod();
}
@@ -167,6 +171,18 @@
}
}
+ private static class InvokedFromLambdaCreatedIn extends BasedOnOtherMethod {
+
+ private InvokedFromLambdaCreatedIn(DexEncodedMethod method) {
+ super(method);
+ }
+
+ @Override
+ String getKind() {
+ return "invoked from lambda created in";
+ }
+ }
+
private static class ReferenedFrom extends BasedOnOtherMethod {
private ReferenedFrom(DexEncodedMethod method) {
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..38473f3 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);
}
}
diff --git a/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java b/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
index 4b7ebd9..b54be6b 100644
--- a/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
+++ b/src/test/examplesAndroidO/lambdadesugaring/LambdaDesugaring.java
@@ -231,7 +231,7 @@
System.out.println(PrivateInit.testPrivate());
System.out.println(g(PrivateInit::new));
- System.out.println(p1(D[]::new));
+ System.out.println(p1(LambdaDesugaring[]::new));
System.out.println(p1(Integer::new));
System.out.println(p1(B::staticArray));
diff --git a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
index 46cd555..ed42134 100644
--- a/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
+++ b/src/test/examplesAndroidO/lambdadesugaringnplus/LambdasWithStaticAndDefaultMethods.java
@@ -327,6 +327,7 @@
@SomeAnnotation(4)
static void annotatedStaticMethod() {
+ synchronized (AnnotatedInterface.class) { } // Do not inline
}
}
@@ -365,6 +366,9 @@
System.out.println("Check 3: NOT OK");
}
+ // I don't know how to keep this method moved to the companion class
+ // without the direct call.
+ AnnotatedInterface.annotatedStaticMethod();
if (checkAnnotationValue(
getCompanionClassOrInterface().getMethod("annotatedStaticMethod").getAnnotations(), 4)) {
System.out.println("Check 4: OK");
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index b6b9ba8..f20335b 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -6,35 +6,66 @@
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.OffOrAuto;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.UnaryOperator;
import org.junit.Test;
+import org.junit.runner.RunWith;
+@RunWith(VmTestRunner.class)
public class R8RunExamplesAndroidOTest extends RunExamplesAndroidOTest<R8Command.Builder> {
+ private static final ArrayList<String> PROGUARD_OPTIONS = Lists.newArrayList(
+ "-keepclasseswithmembers public class * {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "",
+ "-dontobfuscate",
+ "-allowaccessmodification"
+ );
+
+
+ private static final ArrayList<String> PROGUARD_OPTIONS_N_PLUS = Lists.newArrayList(
+ "-keepclasseswithmembers public class * {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "",
+ "-keepclasseswithmembers interface lambdadesugaringnplus."
+ + "LambdasWithStaticAndDefaultMethods$B38302860$AnnotatedInterface{",
+ " *;",
+ "} ",
+ "",
+ "-keepattributes *Annotation*",
+ "-dontobfuscate",
+ "-allowaccessmodification"
+ );
private static Map<DexVm.Version, List<String>> alsoFailsOn =
ImmutableMap.<DexVm.Version, List<String>>builder()
.put(Version.V4_0_4,
ImmutableList.of(
- "invokecustom-with-shrinking"
+ "invokecustom-with-shrinking"
))
.put(Version.V4_4_4,
ImmutableList.of(
- "invokecustom-with-shrinking"
+ "invokecustom-with-shrinking"
))
.put(Version.V5_1_1,
ImmutableList.of(
- "invokecustom-with-shrinking"
+ "invokecustom-with-shrinking"
))
.put(Version.V6_0_1,
ImmutableList.of(
- "invokecustom-with-shrinking"
+ "invokecustom-with-shrinking"
))
.put(Version.V7_0_0,
ImmutableList.of(
@@ -55,6 +86,82 @@
.run();
}
+ @Override
+ @Test
+ public void lambdaDesugaring() throws Throwable {
+ test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
+ .withMinApiLevel(AndroidApiLevel.K)
+ .withOptionConsumer(opts -> opts.enableClassInlining = false)
+ .withBuilderTransformation(
+ b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .run();
+
+ test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
+ .withMinApiLevel(AndroidApiLevel.K)
+ .withOptionConsumer(opts -> opts.enableClassInlining = true)
+ .withBuilderTransformation(
+ b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .run();
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void lambdaDesugaringWithDefaultMethods() throws Throwable {
+ test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
+ .withMinApiLevel(AndroidApiLevel.N)
+ .withOptionConsumer(opts -> opts.enableClassInlining = false)
+ .withBuilderTransformation(
+ b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .run();
+
+ test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
+ .withMinApiLevel(AndroidApiLevel.N)
+ .withOptionConsumer(opts -> opts.enableClassInlining = true)
+ .withBuilderTransformation(
+ b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+ .run();
+ }
+
+ @Override
+ @Test
+ public void lambdaDesugaringNPlus() throws Throwable {
+ test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+ .withMinApiLevel(AndroidApiLevel.K)
+ .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+ .withOptionConsumer(opts -> opts.enableClassInlining = false)
+ .withBuilderTransformation(
+ b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .run();
+
+ test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+ .withMinApiLevel(AndroidApiLevel.K)
+ .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+ .withOptionConsumer(opts -> opts.enableClassInlining = true)
+ .withBuilderTransformation(
+ b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .run();
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void lambdaDesugaringNPlusWithDefaultMethods() throws Throwable {
+ test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+ .withMinApiLevel(AndroidApiLevel.N)
+ .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+ .withOptionConsumer(opts -> opts.enableClassInlining = false)
+ .withBuilderTransformation(
+ b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .run();
+
+ test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
+ .withMinApiLevel(AndroidApiLevel.N)
+ .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+ .withOptionConsumer(opts -> opts.enableClassInlining = true)
+ .withBuilderTransformation(
+ b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+ .run();
+ }
+
class R8TestRunner extends TestRunner<R8TestRunner> {
R8TestRunner(String testName, String packageName, String mainClass) {
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..2667ce2 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -745,10 +745,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 +758,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/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/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/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 81e12a1..dc2774a 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -10,6 +10,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.code.InvokeStatic;
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.code.SgetObject;
import com.android.tools.r8.graph.DexClass;
@@ -19,8 +20,10 @@
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.Collection;
+import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -80,25 +83,25 @@
assertEquals(
Sets.newHashSet(),
- collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateless"));
+ collectAccessedTypes(lambdaCheck, clazz, "testStateless"));
assertEquals(
Sets.newHashSet(),
- collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateful"));
+ collectAccessedTypes(lambdaCheck, clazz, "testStateful"));
assertFalse(
inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1").isPresent());
assertEquals(
Sets.newHashSet(),
- collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateful2"));
+ collectAccessedTypes(lambdaCheck, clazz, "testStateful2"));
assertFalse(
inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful2$1").isPresent());
assertEquals(
Sets.newHashSet(),
- collectAccessedLambdaTypes(lambdaCheck, clazz, "testStateful3"));
+ collectAccessedTypes(lambdaCheck, clazz, "testStateful3"));
assertFalse(
inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful3$1").isPresent());
@@ -138,7 +141,7 @@
assertEquals(
Sets.newHashSet(),
- collectAccessedLambdaTypes(lambdaCheck, clazz,
+ collectAccessedTypes(lambdaCheck, clazz,
"testKotlinSequencesStateless", "kotlin.sequences.Sequence"));
assertFalse(inspector.clazz(
@@ -146,7 +149,7 @@
assertEquals(
Sets.newHashSet(),
- collectAccessedLambdaTypes(lambdaCheck, clazz,
+ collectAccessedTypes(lambdaCheck, clazz,
"testKotlinSequencesStateful", "int", "int", "kotlin.sequences.Sequence"));
assertFalse(inspector.clazz(
@@ -154,7 +157,7 @@
assertEquals(
Sets.newHashSet(),
- collectAccessedLambdaTypes(lambdaCheck, clazz, "testBigExtraMethod"));
+ collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethod"));
assertFalse(inspector.clazz(
"class_inliner_lambda_k_style.MainKt$testBigExtraMethod$1").isPresent());
@@ -165,7 +168,7 @@
assertEquals(
Sets.newHashSet(),
- collectAccessedLambdaTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda"));
+ collectAccessedTypes(lambdaCheck, clazz, "testBigExtraMethodReturningLambda"));
assertFalse(inspector.clazz(
"class_inliner_lambda_k_style.MainKt$testBigExtraMethodReturningLambda$1")
@@ -179,8 +182,25 @@
});
}
- private Set<String> collectAccessedLambdaTypes(
- Predicate<DexType> isLambdaType, ClassSubject clazz, String methodName, String... params) {
+ @Test
+ public void testDataClass() throws Exception {
+ final String mainClassName = "class_inliner_data_class.MainKt";
+ runTest("class_inliner_data_class", mainClassName, true, (app) -> {
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(mainClassName);
+ assertTrue(collectAccessedTypes(
+ type -> !type.toSourceString().startsWith("java."),
+ clazz, "main", String[].class.getCanonicalName()).isEmpty());
+ assertEquals(
+ Lists.newArrayList(
+ "void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)"
+ ),
+ collectStaticCalls(clazz, "main", String[].class.getCanonicalName()));
+ });
+ }
+
+ private Set<String> collectAccessedTypes(Predicate<DexType> isTypeOfInterest,
+ ClassSubject clazz, String methodName, String... params) {
assertNotNull(clazz);
MethodSignature signature = new MethodSignature(methodName, "void", params);
DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
@@ -190,7 +210,7 @@
filterInstructionKind(code, SgetObject.class)
.map(insn -> insn.getField().getHolder())
)
- .filter(isLambdaType)
+ .filter(isTypeOfInterest)
.map(DexType::toSourceString)
.collect(Collectors.toSet());
}
@@ -205,4 +225,14 @@
options.enableLambdaMerging = false;
}, inspector);
}
+
+ private List<String> collectStaticCalls(ClassSubject clazz, String methodName, String... params) {
+ assertNotNull(clazz);
+ MethodSignature signature = new MethodSignature(methodName, "void", params);
+ DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+ return filterInstructionKind(code, InvokeStatic.class)
+ .map(insn -> insn.getMethod().toSourceString())
+ .sorted()
+ .collect(Collectors.toList());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index e543d46..46feb00 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -104,6 +104,6 @@
"}",
// Prevent InterfaceWithDefaultMethods from being merged into ClassImplementingInterface
"-keep class " + InterfaceWithDefaultMethods.class.getCanonicalName()),
- this::defaultMethodAbstract);
+ this::defaultMethodKept);
}
}
diff --git a/src/test/kotlinR8TestResources/class_inliner_data_class/main.kt b/src/test/kotlinR8TestResources/class_inliner_data_class/main.kt
new file mode 100644
index 0000000..c8ef79a
--- /dev/null
+++ b/src/test/kotlinR8TestResources/class_inliner_data_class/main.kt
@@ -0,0 +1,21 @@
+// 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 class_inliner_data_class
+
+fun main(args: Array<String>) {
+ val alpha = Alpha("", "m", "")
+ alpha.right = "l"
+ alpha.left = "r"
+ alpha.rotate()
+ println("result: ${alpha.toString()}")
+}
+
+data class Alpha(var left: String, val middle: String, var right: String) {
+ fun rotate() {
+ val t = left
+ left = right
+ right = t
+ }
+}