Merge "Reland "Implicitly add necessary rules for Java reflections in any R8 mode.""
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 16858af..bca3e00 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -490,6 +490,7 @@
ClassNameMapper classNameMapper =
LineNumberOptimizer.run(
application,
+ appView.getGraphLense(),
namingLens,
options.lineNumberOptimization == LineNumberOptimization.IDENTITY_MAPPING);
timing.end();
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 98c523d..dcab5ae 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -117,6 +117,10 @@
return clazz;
}
+ public String qualifiedName() {
+ return clazz + "." + name;
+ }
+
@Override
public String toSmaliString() {
return clazz.toSmaliString() + "->" + name + ":" + type.toSmaliString();
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 84ceac3..d264b87 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -93,6 +93,10 @@
public abstract DexMethod getOriginalMethodSignature(DexMethod method);
+ public abstract DexField getRenamedFieldSignature(DexField originalField);
+
+ public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
+
public abstract DexType lookupType(DexType type);
// This overload can be used when the graph lense is known to be context insensitive.
@@ -140,7 +144,7 @@
return this instanceof IdentityGraphLense;
}
- public boolean assertNotModified(Iterable<DexItem> items) {
+ public <T extends DexItem> boolean assertNotModified(Iterable<T> items) {
for (DexItem item : items) {
if (item instanceof DexClass) {
DexType type = ((DexClass) item).type;
@@ -172,6 +176,16 @@
}
@Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ return originalField;
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ return originalMethod;
+ }
+
+ @Override
public DexType lookupType(DexType type) {
return type;
}
@@ -254,6 +268,24 @@
}
@Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ DexField renamedField =
+ originalFieldSignatures != null
+ ? originalFieldSignatures.inverse().getOrDefault(originalField, originalField)
+ : originalField;
+ return previousLense.getRenamedFieldSignature(renamedField);
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ DexMethod renamedMethod =
+ originalMethodSignatures != null
+ ? originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod)
+ : originalMethod;
+ return previousLense.getRenamedMethodSignature(renamedMethod);
+ }
+
+ @Override
public DexType lookupType(DexType type) {
if (type.isArrayType()) {
synchronized (this) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 631f234..998ebe9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -93,11 +93,11 @@
private Reason computeInliningReason(DexEncodedMethod target) {
if (target.getOptimizationInfo().forceInline()
|| (inliner.appInfo.hasLiveness()
- && inliner.appInfo.withLiveness().forceInline.contains(target))) {
+ && inliner.appInfo.withLiveness().forceInline.contains(target.method))) {
return Reason.FORCE;
}
if (inliner.appInfo.hasLiveness()
- && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
+ && inliner.appInfo.withLiveness().alwaysInline.contains(target.method)) {
return Reason.ALWAYS;
}
if (callSiteInformation.hasSingleCallSite(target)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 12baa2c..1cbd2d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -71,7 +71,7 @@
}
public boolean isBlackListed(DexEncodedMethod method) {
- return blackList.contains(method.method) || appInfo.neverInline.contains(method);
+ return blackList.contains(method.method) || appInfo.neverInline.contains(method.method);
}
private ConstraintWithTarget instructionAllowedForInlining(
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index c5d366a..e405bfb 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -35,10 +35,10 @@
public class ClassNameMapper implements ProguardMap {
public static class Builder extends ProguardMap.Builder {
- final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
+ private final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
private Builder() {
- mapBuilder = ImmutableMap.builder();
+ this.mapBuilder = ImmutableMap.builder();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 34fdd9d..f45a3de 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -14,6 +14,7 @@
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -68,17 +69,17 @@
/** The parameters are forwarded to MappedRange constructor, see explanation there. */
@Override
public void addMappedRange(
- Range obfuscatedRange,
+ Range minifiedRange,
MemberNaming.MethodSignature originalSignature,
Object originalRange,
- String obfuscatedName) {
+ String renamedName) {
mappedRangesByName
- .computeIfAbsent(obfuscatedName, k -> new ArrayList<>())
- .add(new MappedRange(obfuscatedRange, originalSignature, originalRange, obfuscatedName));
+ .computeIfAbsent(renamedName, k -> new ArrayList<>())
+ .add(new MappedRange(minifiedRange, originalSignature, originalRange, renamedName));
}
}
- /** List of MappedRanges that belong to the same obfuscated name. */
+ /** List of MappedRanges that belong to the same renamed name. */
public static class MappedRangesOfName {
private final List<MappedRange> mappedRanges;
@@ -93,14 +94,14 @@
public MappedRange firstRangeForLine(int line) {
MappedRange bestRange = null;
for (MappedRange range : mappedRanges) {
- if (range.obfuscatedRange == null) {
+ if (range.minifiedRange == null) {
if (bestRange == null) {
// This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll
// be no better one.
bestRange = range;
}
- } else if (range.obfuscatedRange.contains(line)) {
- // Concrete obfuscated range found ("x:y:a()[:u[:v]] -> b")
+ } else if (range.minifiedRange.contains(line)) {
+ // Concrete minified range found ("x:y:a()[:u[:v]] -> b")
return range;
}
}
@@ -108,25 +109,25 @@
}
/**
- * Search for a MappedRange where the obfuscated range contains the specified {@code line} and
- * return that and the subsequent MappedRanges with the same obfuscated range. Return general
+ * Search for a MappedRange where the minified range contains the specified {@code line} and
+ * return that and the subsequent MappedRanges with the same minified range. Return general
* MappedRange ("a() -> b") if no concrete mapping found or empty list if nothing found.
*/
public List<MappedRange> allRangesForLine(int line) {
MappedRange noLineRange = null;
for (int i = 0; i < mappedRanges.size(); ++i) {
MappedRange rangeI = mappedRanges.get(i);
- if (rangeI.obfuscatedRange == null) {
+ if (rangeI.minifiedRange == null) {
if (noLineRange == null) {
// This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll
// be no better one.
noLineRange = rangeI;
}
- } else if (rangeI.obfuscatedRange.contains(line)) {
- // Concrete obfuscated range found ("x:y:a()[:u[:v]] -> b")
+ } else if (rangeI.minifiedRange.contains(line)) {
+ // Concrete minified range found ("x:y:a()[:u[:v]] -> b")
int j = i + 1;
for (; j < mappedRanges.size(); ++j) {
- if (!Objects.equals(mappedRanges.get(j).obfuscatedRange, rangeI.obfuscatedRange)) {
+ if (!Objects.equals(mappedRanges.get(j).minifiedRange, rangeI.minifiedRange)) {
break;
}
}
@@ -165,27 +166,27 @@
/**
* Mapping from the renamed signature to the naming information for a member.
- * <p>
- * A renamed signature is a signature where the member's name has been obfuscated but not the type
+ *
+ * <p>A renamed signature is a signature where the member's name has been renamed but not the type
* information.
- **/
+ */
private final ImmutableMap<MethodSignature, MemberNaming> methodMembers;
private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers;
- /** Map of obfuscated name -> MappedRangesOfName */
- public final Map<String, MappedRangesOfName> mappedRangesByName;
+ /** Map of renamed name -> MappedRangesOfName */
+ public final Map<String, MappedRangesOfName> mappedRangesByRenamedName;
private ClassNamingForNameMapper(
String renamedName,
String originalName,
Map<MethodSignature, MemberNaming> methodMembers,
Map<FieldSignature, MemberNaming> fieldMembers,
- Map<String, MappedRangesOfName> mappedRangesByName) {
+ Map<String, MappedRangesOfName> mappedRangesByRenamedName) {
this.renamedName = renamedName;
this.originalName = originalName;
this.methodMembers = ImmutableMap.copyOf(methodMembers);
this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
- this.mappedRangesByName = mappedRangesByName;
+ this.mappedRangesByRenamedName = mappedRangesByRenamedName;
}
@Override
@@ -273,16 +274,13 @@
});
// Sort MappedRanges by sequence number to restore construction order (original Proguard-map
- // input)
- List<MappedRange> rangeList = new ArrayList<>();
- for (MappedRangesOfName ranges : mappedRangesByName.values()) {
- rangeList.addAll(ranges.mappedRanges);
+ // input).
+ List<MappedRange> mappedRangesSorted = new ArrayList<>();
+ for (MappedRangesOfName ranges : mappedRangesByRenamedName.values()) {
+ mappedRangesSorted.addAll(ranges.mappedRanges);
}
- rangeList.sort(
- (lhs, rhs) -> {
- return lhs.sequenceNumber - rhs.sequenceNumber;
- });
- for (MappedRange range : rangeList) {
+ mappedRangesSorted.sort(Comparator.comparingInt(range -> range.sequenceNumber));
+ for (MappedRange range : mappedRangesSorted) {
writer.append(" ").append(range.toString()).append('\n');
}
}
@@ -313,7 +311,7 @@
&& renamedName.equals(that.renamedName)
&& methodMembers.equals(that.methodMembers)
&& fieldMembers.equals(that.fieldMembers)
- && mappedRangesByName.equals(that.mappedRangesByName);
+ && mappedRangesByRenamedName.equals(that.mappedRangesByRenamedName);
}
@Override
@@ -322,27 +320,27 @@
result = 31 * result + renamedName.hashCode();
result = 31 * result + methodMembers.hashCode();
result = 31 * result + fieldMembers.hashCode();
- result = 31 * result + mappedRangesByName.hashCode();
+ result = 31 * result + mappedRangesByRenamedName.hashCode();
return result;
}
/**
- * MappedRange describes an (original line numbers, signature) <-> (obfuscated line numbers)
+ * MappedRange describes an (original line numbers, signature) <-> (minified line numbers)
* mapping. It can describe 3 different things:
*
* <p>1. The method is renamed. The original source lines are preserved. The corresponding
* Proguard-map syntax is "a(...) -> b"
*
- * <p>2. The source lines of a method in the original range are renumbered to the obfuscated
- * range. In this case the {@link MappedRange#originalRange} is either a {@code Range} or null,
+ * <p>2. The source lines of a method in the original range are renumbered to the minified range.
+ * In this case the {@link MappedRange#originalRange} is either a {@code Range} or null,
* indicating that the original range is unknown or is the same as the {@link
- * MappedRange#obfuscatedRange}. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or
+ * MappedRange#minifiedRange}. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or
* "x:y:a(...):u:v -> b"
*
* <p>3. The source line of a method is the inlining caller of the previous {@code MappedRange}.
* In this case the {@link MappedRange@originalRange} is either an {@code int} or null, indicating
- * that the original source line is unknown, or may be identical to a line of the obfuscated
- * range. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or "x:y:a(...):u -> b"
+ * that the original source line is unknown, or may be identical to a line of the minified range.
+ * The corresponding Proguard-map syntax is "x:y:a(...) -> b" or "x:y:a(...):u -> b"
*/
public static class MappedRange {
@@ -352,10 +350,10 @@
return nextSequenceNumber++;
}
- public final Range obfuscatedRange; // Can be null, if so then originalRange must also be null.
+ public final Range minifiedRange; // Can be null, if so then originalRange must also be null.
public final MethodSignature signature;
public final Object originalRange; // null, Integer or Range.
- public final String obfuscatedName;
+ public final String renamedName;
/**
* The sole purpose of {@link #sequenceNumber} is to preserve the order of members read from a
@@ -364,52 +362,49 @@
private final int sequenceNumber = getNextSequenceNumber();
private MappedRange(
- Range obfuscatedRange,
- MethodSignature signature,
- Object originalRange,
- String obfuscatedName) {
+ Range minifiedRange, MethodSignature signature, Object originalRange, String renamedName) {
- assert obfuscatedRange != null || originalRange == null;
+ assert minifiedRange != null || originalRange == null;
assert originalRange == null
|| originalRange instanceof Integer
|| originalRange instanceof Range;
- this.obfuscatedRange = obfuscatedRange;
+ this.minifiedRange = minifiedRange;
this.signature = signature;
this.originalRange = originalRange;
- this.obfuscatedName = obfuscatedName;
+ this.renamedName = renamedName;
}
- public int originalLineFromObfuscated(int obfuscatedLineNumber) {
- if (obfuscatedRange == null) {
+ public int getOriginalLineNumber(int lineNumberAfterMinification) {
+ if (minifiedRange == null) {
// General mapping without concrete line numbers: "a() -> b"
- return obfuscatedLineNumber;
+ return lineNumberAfterMinification;
}
- assert obfuscatedRange.contains(obfuscatedLineNumber);
+ assert minifiedRange.contains(lineNumberAfterMinification);
if (originalRange == null) {
// Concrete identity mapping: "x:y:a() -> b"
- return obfuscatedLineNumber;
+ return lineNumberAfterMinification;
} else if (originalRange instanceof Integer) {
// Inlinee: "x:y:a():u -> b"
return (int) originalRange;
} else {
// "x:y:a():u:v -> b"
assert originalRange instanceof Range;
- return ((Range) originalRange).from + obfuscatedLineNumber - obfuscatedRange.from;
+ return ((Range) originalRange).from + lineNumberAfterMinification - minifiedRange.from;
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- if (obfuscatedRange != null) {
- builder.append(obfuscatedRange).append(':');
+ if (minifiedRange != null) {
+ builder.append(minifiedRange).append(':');
}
builder.append(signature);
if (originalRange != null) {
builder.append(":").append(originalRange);
}
- builder.append(" -> ").append(obfuscatedName);
+ builder.append(" -> ").append(renamedName);
return builder.toString();
}
@@ -426,19 +421,19 @@
MappedRange that = (MappedRange) o;
- return Objects.equals(obfuscatedRange, that.obfuscatedRange)
+ return Objects.equals(minifiedRange, that.minifiedRange)
&& Objects.equals(originalRange, that.originalRange)
&& signature.equals(that.signature)
- && obfuscatedName.equals(that.obfuscatedName);
+ && renamedName.equals(that.renamedName);
}
@Override
public int hashCode() {
// sequenceNumber is intentionally omitted from hashCode since it's not used in equality test.
- int result = Objects.hashCode(obfuscatedRange);
+ int result = Objects.hashCode(minifiedRange);
result = 31 * result + Objects.hashCode(originalRange);
result = 31 * result + signature.hashCode();
- result = 31 * result + obfuscatedName.hashCode();
+ result = 31 * result + renamedName.hashCode();
return result;
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index c766c37..094dcaf 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -134,7 +134,12 @@
}
public static FieldSignature fromDexField(DexField field) {
- return new FieldSignature(field.name.toSourceString(),
+ return fromDexField(field, false);
+ }
+
+ public static FieldSignature fromDexField(DexField field, boolean withQualifiedName) {
+ return new FieldSignature(
+ withQualifiedName ? field.qualifiedName() : field.name.toSourceString(),
field.type.toSourceString());
}
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 6ba18ac..46fdc82 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -100,6 +100,17 @@
}
@Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ return previousLense.getRenamedFieldSignature(originalField);
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ // TODO(b/79143143): implement this when re-enabling bridge analysis.
+ throw new Unimplemented("BridgeLense.getRenamedMethodSignature() not implemented");
+ }
+
+ @Override
public DexType lookupType(DexType type) {
return previousLense.lookupType(type);
}
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 420af47..64387ce 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1672,15 +1672,15 @@
/**
* All methods that should be inlined if possible due to a configuration directive.
*/
- public final Set<DexItem> alwaysInline;
+ public final Set<DexMethod> alwaysInline;
/**
* All methods that *must* be inlined due to a configuration directive (testing only).
*/
- public final Set<DexItem> forceInline;
+ public final Set<DexMethod> forceInline;
/**
* All methods that *must* never be inlined due to a configuration directive (testing only).
*/
- public final Set<DexItem> neverInline;
+ public final Set<DexMethod> neverInline;
/**
* All items with -identifiernamestring rule.
* Bound boolean value indicates the rule is explicitly specified by users (<code>true</code>)
@@ -1829,8 +1829,8 @@
this.assumedValues = previous.assumedValues;
assert lense.assertNotModified(previous.alwaysInline);
this.alwaysInline = previous.alwaysInline;
- this.forceInline = previous.forceInline;
- this.neverInline = previous.neverInline;
+ this.forceInline = rewriteMethodsWithRenamedSignature(previous.forceInline, lense);
+ this.neverInline = rewriteMethodsWithRenamedSignature(previous.neverInline, lense);
this.identifierNameStrings =
rewriteMixedItemsConservatively(previous.identifierNameStrings, lense);
// Switchmap classes should never be affected by renaming.
@@ -1954,6 +1954,16 @@
return builder.build();
}
+ private static ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(
+ Set<DexMethod> methods, GraphLense lense) {
+ ImmutableSortedSet.Builder<DexMethod> builder =
+ new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
+ for (DexMethod method : methods) {
+ builder.add(lense.getRenamedMethodSignature(method));
+ }
+ return builder.build();
+ }
+
private static ImmutableSortedSet<DexMethod> rewriteMethodsConservatively(
Set<DexMethod> original, GraphLense lense) {
ImmutableSortedSet.Builder<DexMethod> builder =
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index c95cfe9..473c990 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -59,9 +59,9 @@
private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
Sets.newIdentityHashSet();
private final Set<DexItem> checkDiscarded = Sets.newIdentityHashSet();
- private final Set<DexItem> alwaysInline = Sets.newIdentityHashSet();
- private final Set<DexItem> forceInline = Sets.newIdentityHashSet();
- private final Set<DexItem> neverInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
@@ -727,13 +727,19 @@
} else if (context instanceof InlineRule) {
switch (((InlineRule) context).getType()) {
case ALWAYS:
- alwaysInline.add(item);
+ if (item instanceof DexEncodedMethod) {
+ alwaysInline.add(((DexEncodedMethod) item).method);
+ }
break;
case FORCE:
- forceInline.add(item);
+ if (item instanceof DexEncodedMethod) {
+ forceInline.add(((DexEncodedMethod) item).method);
+ }
break;
case NEVER:
- neverInline.add(item);
+ if (item instanceof DexEncodedMethod) {
+ neverInline.add(((DexEncodedMethod) item).method);
+ }
break;
default:
throw new Unreachable();
@@ -755,9 +761,9 @@
public final Set<DexItem> reasonAsked;
public final Set<DexItem> keepPackageName;
public final Set<DexItem> checkDiscarded;
- public final Set<DexItem> alwaysInline;
- public final Set<DexItem> forceInline;
- public final Set<DexItem> neverInline;
+ public final Set<DexMethod> alwaysInline;
+ public final Set<DexMethod> forceInline;
+ public final Set<DexMethod> neverInline;
public final Map<DexItem, ProguardMemberRule> noSideEffects;
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
@@ -792,9 +798,9 @@
Set<DexItem> reasonAsked,
Set<DexItem> keepPackageName,
Set<DexItem> checkDiscarded,
- Set<DexItem> alwaysInline,
- Set<DexItem> forceInline,
- Set<DexItem> neverInline,
+ Set<DexMethod> alwaysInline,
+ Set<DexMethod> forceInline,
+ Set<DexMethod> neverInline,
Map<DexItem, ProguardMemberRule> noSideEffects,
Map<DexItem, ProguardMemberRule> assumedValues,
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
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 ee596f3..a2dd383 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -252,7 +252,7 @@
}
}
- private void extractPinnedItems(Iterable<DexItem> items, AbortReason reason) {
+ private <T extends DexItem> void extractPinnedItems(Iterable<T> items, AbortReason reason) {
for (DexItem item : items) {
if (item instanceof DexType || item instanceof DexClass) {
DexType type = item instanceof DexType ? (DexType) item : ((DexClass) item).type;
@@ -1524,6 +1524,16 @@
}
@Override
+ public DexField getRenamedFieldSignature(DexField originalField) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+ throw new Unreachable();
+ }
+
+ @Override
public DexType lookupType(DexType type) {
return type == source ? target : mergedClasses.getOrDefault(type, type);
}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 5163772..66490b2 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNaming;
@@ -35,6 +36,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Function;
import java.util.function.Supplier;
public class LineNumberOptimizer {
@@ -202,7 +204,10 @@
}
public static ClassNameMapper run(
- DexApplication application, NamingLens namingLens, boolean identityMapping) {
+ DexApplication application,
+ GraphLense graphLense,
+ NamingLens namingLens,
+ boolean identityMapping) {
ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
// Collect which files contain which classes that need to have their line numbers optimized.
for (DexProgramClass clazz : application.classes()) {
@@ -212,8 +217,8 @@
continue;
}
- IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
- groupMethodsByName(namingLens, clazz);
+ IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
+ groupMethodsByRenamedName(namingLens, clazz);
// At this point we don't know if we really need to add this class to the builder.
// It depends on whether any methods/fields are renamed or some methods contain positions.
@@ -230,10 +235,13 @@
addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
// First transfer renamed fields to classNamingBuilder.
- addFieldsToClassNaming(namingLens, clazz, onDemandClassNamingBuilder);
+ addFieldsToClassNaming(graphLense, namingLens, clazz, onDemandClassNamingBuilder);
- // Then process the methods.
- for (List<DexEncodedMethod> methods : methodsByName.values()) {
+ // Then process the methods, ordered by renamed name.
+ List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
+ renamedMethodNames.sort(DexString::slowCompareTo);
+ for (DexString methodName : renamedMethodNames) {
+ List<DexEncodedMethod> methods = methodsByRenamedName.get(methodName);
if (methods.size() > 1) {
// If there are multiple methods with the same name (overloaded) then sort them for
// deterministic behaviour: the algorithm will assign new line numbers in this order.
@@ -258,7 +266,9 @@
}
}
- MethodSignature originalSignature = MethodSignature.fromDexMethod(method.method);
+ DexMethod originalMethod = graphLense.getOriginalMethodSignature(method.method);
+ MethodSignature originalSignature =
+ MethodSignature.fromDexMethod(originalMethod, originalMethod.holder != clazz.type);
DexString obfuscatedNameDexString = namingLens.lookupName(method.method);
String obfuscatedName = obfuscatedNameDexString.toString();
@@ -266,7 +276,8 @@
// Add simple "a() -> b" mapping if we won't have any other with concrete line numbers
if (mappedPositions.isEmpty()) {
// But only if it's been renamed.
- if (obfuscatedNameDexString != method.method.name) {
+ if (obfuscatedNameDexString != originalMethod.name
+ || originalMethod.holder != clazz.type) {
onDemandClassNamingBuilder
.get()
.addMappedRange(null, originalSignature, null, obfuscatedName);
@@ -275,7 +286,16 @@
}
Map<DexMethod, MethodSignature> signatures = new IdentityHashMap<>();
- signatures.put(method.method, originalSignature);
+ signatures.put(originalMethod, originalSignature);
+ Function<DexMethod, MethodSignature> getOriginalMethodSignature =
+ m -> {
+ DexMethod original = graphLense.getOriginalMethodSignature(m);
+ return signatures.computeIfAbsent(
+ original,
+ key ->
+ MethodSignature.fromDexMethod(
+ original, original.holder != clazz.getType()));
+ };
MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
@@ -306,23 +326,14 @@
ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.get();
classNamingBuilder.addMappedRange(
obfuscatedRange,
- signatures.computeIfAbsent(
- firstPosition.method,
- m ->
- MethodSignature.fromDexMethod(
- m, firstPosition.method.holder != clazz.getType())),
+ getOriginalMethodSignature.apply(firstPosition.method),
originalRange,
obfuscatedName);
Position caller = firstPosition.caller;
while (caller != null) {
- Position finalCaller = caller;
classNamingBuilder.addMappedRange(
obfuscatedRange,
- signatures.computeIfAbsent(
- caller.method,
- m ->
- MethodSignature.fromDexMethod(
- m, finalCaller.method.holder != clazz.getType())),
+ getOriginalMethodSignature.apply(caller.method),
Math.max(caller.line, 0), // Prevent against "no-position".
obfuscatedName);
caller = caller.callerPosition;
@@ -378,42 +389,37 @@
}
}
- private static void addFieldsToClassNaming(NamingLens namingLens, DexProgramClass clazz,
+ private static void addFieldsToClassNaming(
+ GraphLense graphLense,
+ NamingLens namingLens,
+ DexProgramClass clazz,
Supplier<Builder> onDemandClassNamingBuilder) {
clazz.forEachField(
dexEncodedField -> {
DexField dexField = dexEncodedField.field;
+ DexField originalField = graphLense.getOriginalFieldSignature(dexField);
DexString renamedName = namingLens.lookupName(dexField);
- if (renamedName != dexField.name) {
- FieldSignature signature =
- new FieldSignature(dexField.name.toString(), dexField.type.toString());
- MemberNaming memberNaming = new MemberNaming(signature, renamedName.toString());
+ if (renamedName != originalField.name || originalField.clazz != clazz.type) {
+ FieldSignature originalSignature =
+ FieldSignature.fromDexField(originalField, originalField.clazz != clazz.type);
+ MemberNaming memberNaming = new MemberNaming(originalSignature, renamedName.toString());
onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
}
});
}
- private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByName(
+ private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByRenamedName(
NamingLens namingLens, DexProgramClass clazz) {
- IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
+ IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
- clazz.forEachMethod(
- method -> {
- // Add method only if renamed or contains positions.
- if (namingLens.lookupName(method.method) != method.method.name
- || doesContainPositions(method)) {
- methodsByName.compute(
- method.method.name,
- (name, methods) -> {
- if (methods == null) {
- methods = new ArrayList<>();
- }
- methods.add(method);
- return methods;
- });
- }
- });
- return methodsByName;
+ for (DexEncodedMethod method : clazz.methods()) {
+ // Add method only if renamed or contains positions.
+ DexString renamedName = namingLens.lookupName(method.method);
+ if (renamedName != method.method.name || doesContainPositions(method)) {
+ methodsByRenamedName.computeIfAbsent(renamedName, key -> new ArrayList<>()).add(method);
+ }
+ }
+ return methodsByRenamedName;
}
private static boolean doesContainPositions(DexEncodedMethod method) {
diff --git a/src/test/examples/classmerging/ProguardFieldMapTest.java b/src/test/examples/classmerging/ProguardFieldMapTest.java
new file mode 100644
index 0000000..0cbf8a1
--- /dev/null
+++ b/src/test/examples/classmerging/ProguardFieldMapTest.java
@@ -0,0 +1,25 @@
+// 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 ProguardFieldMapTest {
+
+ public static void main(String[] args) {
+ B b = new B();
+ b.test();
+ }
+
+ public static class A {
+
+ public String f = "A.f";
+ }
+
+ public static class B extends A {
+
+ public void test() {
+ System.out.println(f);
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/ProguardMethodMapTest.java b/src/test/examples/classmerging/ProguardMethodMapTest.java
new file mode 100644
index 0000000..9ce0ad7
--- /dev/null
+++ b/src/test/examples/classmerging/ProguardMethodMapTest.java
@@ -0,0 +1,29 @@
+// 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 ProguardMethodMapTest {
+
+ public static void main(String[] args) {
+ B b = new B();
+ b.method();
+ }
+
+ public static class A {
+
+ public void method() {
+ System.out.println("In A.method()");
+ }
+ }
+
+ public static class B extends A {
+
+ @Override
+ public void method() {
+ System.out.println("In B.method()");
+ super.method();
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 37a3010..3a70504 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -37,6 +37,12 @@
-keep public class classmerging.PinnedParameterTypesTest$TestClass {
public static void method(...);
}
+-keep public class classmerging.ProguardFieldMapTest {
+ public static void main(...);
+}
+-keep public class classmerging.ProguardMethodMapTest {
+ public static void main(...);
+}
-keep public class classmerging.SimpleInterfaceAccessTest {
public static void main(...);
}
diff --git a/src/test/examples/shaking1/print-mapping-cf.ref b/src/test/examples/shaking1/print-mapping-cf.ref
new file mode 100644
index 0000000..9bf8013
--- /dev/null
+++ b/src/test/examples/shaking1/print-mapping-cf.ref
@@ -0,0 +1,7 @@
+shaking1.Shaking -> shaking1.Shaking:
+shaking1.Used -> a.a:
+ java.lang.String name -> a
+ 1:1:java.lang.String method():17:17 -> a
+ 1:1:void main(java.lang.String[]):8:8 -> main
+ 1:2:void <init>(java.lang.String):12:13 -> <init>
+ 1:1:java.lang.String aMethodThatIsNotUsedButKept():21:21 -> aMethodThatIsNotUsedButKept
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 6f3c97a..6322840 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -3,9 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.classmerging;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
import static com.android.tools.r8.smali.SmaliBuilder.buildCode;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
@@ -14,6 +16,7 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer.FileConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -21,6 +24,10 @@
import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.MoveException;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.debug.DexDebugTestConfig;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.jasmin.JasminBuilder;
@@ -34,8 +41,11 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -97,7 +107,7 @@
);
@Test
- public void testClassesHaveBeenMerged() throws Exception {
+ public void testClassesHaveBeenMerged() throws Throwable {
runR8(EXAMPLE_KEEP, this::configure);
// GenericInterface should be merged into GenericInterfaceImpl.
for (String candidate : CAN_BE_MERGED) {
@@ -109,7 +119,7 @@
}
@Test
- public void testClassesHaveNotBeenMerged() throws Exception {
+ public void testClassesHaveNotBeenMerged() throws Throwable {
runR8(DONT_OPTIMIZE, null);
for (String candidate : CAN_BE_MERGED) {
assertTrue(inspector.clazz(candidate).isPresent());
@@ -124,7 +134,7 @@
// Assuming that the chance of observing one of the two orderings is 50%, this test therefore has
// approximately 3% chance of succeeding although there is an issue.
@Test
- public void testCallGraphCycle() throws Exception {
+ public void testCallGraphCycle() throws Throwable {
String main = "classmerging.CallGraphCycleTest";
Path[] programFiles =
new Path[] {
@@ -140,7 +150,7 @@
}
@Test
- public void testConflictInGeneratedName() throws Exception {
+ public void testConflictInGeneratedName() throws Throwable {
String main = "classmerging.ConflictInGeneratedNameTest";
Path[] programFiles =
new Path[] {
@@ -162,7 +172,9 @@
configure(options);
// Avoid that direct methods in B get inlined.
options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
- });
+ },
+ // Disable debug testing since the test has a method with "$classmerging$" in the name.
+ null);
ClassSubject clazzSubject = inspector.clazz("classmerging.ConflictInGeneratedNameTest$B");
assertThat(clazzSubject, isPresent());
@@ -204,14 +216,14 @@
}
@Test
- public void testConflictWasDetected() throws Exception {
+ public void testConflictWasDetected() throws Throwable {
runR8(EXAMPLE_KEEP, this::configure);
assertTrue(inspector.clazz("classmerging.ConflictingInterface").isPresent());
assertTrue(inspector.clazz("classmerging.ConflictingInterfaceImpl").isPresent());
}
@Test
- public void testFieldCollision() throws Exception {
+ public void testFieldCollision() throws Throwable {
String main = "classmerging.FieldCollisionTest";
Path[] programFiles =
new Path[] {
@@ -227,7 +239,7 @@
}
@Test
- public void testLambdaRewriting() throws Exception {
+ public void testLambdaRewriting() throws Throwable {
String main = "classmerging.LambdaRewritingTest";
Path[] programFiles =
new Path[] {
@@ -250,7 +262,7 @@
}
@Test
- public void testMethodCollision() throws Exception {
+ public void testMethodCollision() throws Throwable {
String main = "classmerging.MethodCollisionTest";
Path[] programFiles =
new Path[] {
@@ -275,7 +287,7 @@
}
@Test
- public void testNestedDefaultInterfaceMethodsTest() throws Exception {
+ public void testNestedDefaultInterfaceMethodsTest() throws Throwable {
String main = "classmerging.NestedDefaultInterfaceMethodsTest";
Path[] programFiles =
new Path[] {
@@ -294,7 +306,7 @@
}
@Test
- public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Exception {
+ public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Throwable {
String main = "classmerging.NestedDefaultInterfaceMethodsTest";
Path[] programFiles =
new Path[] {
@@ -319,7 +331,7 @@
}
@Test
- public void testPinnedParameterTypes() throws Exception {
+ public void testPinnedParameterTypes() throws Throwable {
String main = "classmerging.PinnedParameterTypesTest";
Path[] programFiles =
new Path[] {
@@ -342,7 +354,210 @@
}
@Test
- public void testSuperCallWasDetected() throws Exception {
+ public void testProguardFieldMap() throws Throwable {
+ String main = "classmerging.ProguardFieldMapTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ProguardFieldMapTest.class"),
+ CF_DIR.resolve("ProguardFieldMapTest$A.class"),
+ CF_DIR.resolve("ProguardFieldMapTest$B.class")
+ };
+
+ // Try without vertical class merging, to check that output is as expected.
+ String expectedProguardMapWithoutClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardFieldMapTest -> classmerging.ProguardFieldMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardFieldMapTest$A -> classmerging.ProguardFieldMapTest$A:",
+ " 1:1:void <init>():14:14 -> <init>",
+ " 2:2:void <init>():16:16 -> <init>",
+ "classmerging.ProguardFieldMapTest$B -> classmerging.ProguardFieldMapTest$B:",
+ " 1:1:void <init>():19:19 -> <init>",
+ " 1:1:void test():22:22 -> test");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ Predicates.alwaysTrue(),
+ getProguardConfig(EXAMPLE_KEEP),
+ options -> {
+ configure(options);
+ options.enableClassMerging = false;
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithoutClassMerging));
+ });
+
+ // Try with vertical class merging.
+ String expectedProguardMapWithClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardFieldMapTest -> classmerging.ProguardFieldMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardFieldMapTest$B -> classmerging.ProguardFieldMapTest$B:",
+ " java.lang.String classmerging.ProguardFieldMapTest$A.f -> f",
+ " 1:1:void classmerging.ProguardFieldMapTest$A.<init>():14:14 -> <init>",
+ " 1:1:void <init>():19 -> <init>",
+ " 2:2:void classmerging.ProguardFieldMapTest$A.<init>():16:16 -> <init>",
+ " 2:2:void <init>():19 -> <init>",
+ " 1:1:void test():22:22 -> test");
+ Set<String> preservedClassNames =
+ ImmutableSet.of("classmerging.ProguardFieldMapTest", "classmerging.ProguardFieldMapTest$B");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ preservedClassNames::contains,
+ getProguardConfig(EXAMPLE_KEEP),
+ options -> {
+ configure(options);
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging));
+ });
+ }
+
+ @Test
+ public void testProguardMethodMap() throws Throwable {
+ String main = "classmerging.ProguardMethodMapTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ProguardMethodMapTest.class"),
+ CF_DIR.resolve("ProguardMethodMapTest$A.class"),
+ CF_DIR.resolve("ProguardMethodMapTest$B.class")
+ };
+
+ // Try without vertical class merging, to check that output is as expected.
+ String expectedProguardMapWithoutClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardMethodMapTest$A -> classmerging.ProguardMethodMapTest$A:",
+ " 1:1:void <init>():14:14 -> <init>",
+ " 1:1:void method():17:17 -> method",
+ "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+ " 1:1:void <init>():21:21 -> <init>",
+ " 1:2:void method():25:26 -> method");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ Predicates.alwaysTrue(),
+ getProguardConfig(EXAMPLE_KEEP),
+ options -> {
+ configure(options);
+ options.enableClassMerging = false;
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithoutClassMerging));
+ });
+
+ // Try with vertical class merging.
+ String expectedProguardMapWithClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+ // TODO(christofferqa): Should this be "...<init>():14 -> <init>" to reflect that
+ // A.<init> has been inlined into B.<init>?
+ " 1:1:void classmerging.ProguardMethodMapTest$A.<init>():14:14 -> <init>",
+ // TODO(christofferqa): Should this be " ...<init>():21:21 -> <init>"?
+ " 1:1:void <init>():21 -> <init>",
+ " 1:2:void method():25:26 -> method",
+ " 1:1:void classmerging.ProguardMethodMapTest$A.method():17:17 -> "
+ + "method$classmerging$ProguardMethodMapTest$A");
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ preservedClassNames::contains,
+ getProguardConfig(EXAMPLE_KEEP),
+ options -> {
+ configure(options);
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging));
+ });
+ }
+
+ @Test
+ public void testProguardMethodMapAfterInlining() throws Throwable {
+ String main = "classmerging.ProguardMethodMapTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ProguardMethodMapTest.class"),
+ CF_DIR.resolve("ProguardMethodMapTest$A.class"),
+ CF_DIR.resolve("ProguardMethodMapTest$B.class")
+ };
+
+ // Try without vertical class merging, to check that output is as expected.
+ String expectedProguardMapWithoutClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardMethodMapTest$A -> classmerging.ProguardMethodMapTest$A:",
+ " 1:1:void <init>():14:14 -> <init>",
+ "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+ " 1:1:void <init>():21:21 -> <init>",
+ " 1:1:void method():25:25 -> method",
+ " 2:2:void classmerging.ProguardMethodMapTest$A.method():17:17 -> method",
+ " 2:2:void method():26 -> method");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ Predicates.alwaysTrue(),
+ getProguardConfig(
+ EXAMPLE_KEEP,
+ "-forceinline class classmerging.ProguardMethodMapTest$A { public void method(); }"),
+ options -> {
+ configure(options);
+ options.enableClassMerging = false;
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithoutClassMerging));
+ });
+
+ // Try with vertical class merging.
+ String expectedProguardMapWithClassMerging =
+ Joiner.on(System.lineSeparator())
+ .join(
+ "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+ " 1:2:void main(java.lang.String[]):10:11 -> main",
+ "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+ " 1:1:void classmerging.ProguardMethodMapTest$A.<init>():14:14 -> <init>",
+ " 1:1:void <init>():21 -> <init>",
+ " 1:1:void method():25:25 -> method",
+ " 2:2:void classmerging.ProguardMethodMapTest$A.method():17:17 -> method",
+ " 2:2:void method():26 -> method");
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
+ runTestOnInput(
+ main,
+ readProgramFiles(programFiles),
+ preservedClassNames::contains,
+ getProguardConfig(
+ EXAMPLE_KEEP,
+ "-forceinline class classmerging.ProguardMethodMapTest$A { public void method(); }"),
+ options -> {
+ configure(options);
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ options.proguardMapConsumer =
+ (proguardMap, handler) ->
+ assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging));
+ });
+ }
+
+ @Test
+ public void testSuperCallWasDetected() throws Throwable {
String main = "classmerging.SuperCallRewritingTest";
Path[] programFiles =
new Path[] {
@@ -366,7 +581,7 @@
// targets SubClassThatReferencesSuperMethod.referencedMethod. When running without class
// merging, R8 should not rewrite the invoke-super instruction into invoke-direct.
@Test
- public void testSuperCallNotRewrittenToDirect() throws Exception {
+ public void testSuperCallNotRewrittenToDirect() throws Throwable {
String main = "classmerging.SuperCallRewritingTest";
Path[] programFiles =
new Path[] {
@@ -446,7 +661,7 @@
// }
@Test
@IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
- public void testSuperCallToMergedClassIsRewritten() throws Exception {
+ public void testSuperCallToMergedClassIsRewritten() throws Throwable {
String main = "classmerging.SuperCallToMergedClassIsRewrittenTest";
Set<String> preservedClassNames =
ImmutableSet.of(
@@ -569,7 +784,7 @@
}
@Test
- public void testConflictingInterfaceSignatures() throws Exception {
+ public void testConflictingInterfaceSignatures() throws Throwable {
String main = "classmerging.ConflictingInterfaceSignaturesTest";
Path[] programFiles =
new Path[] {
@@ -588,7 +803,7 @@
// If an exception class A is merged into another exception class B, then all exception tables
// should be updated, and class A should be removed entirely.
@Test
- public void testExceptionTables() throws Exception {
+ public void testExceptionTables() throws Throwable {
String main = "classmerging.ExceptionTest";
Path[] programFiles =
new Path[] {
@@ -624,7 +839,7 @@
}
@Test
- public void testMergeDefaultMethodIntoClass() throws Exception {
+ public void testMergeDefaultMethodIntoClass() throws Throwable {
String main = "classmerging.MergeDefaultMethodIntoClassTest";
Path[] programFiles =
new Path[] {
@@ -658,7 +873,7 @@
}
@Test
- public void testNativeMethod() throws Exception {
+ public void testNativeMethod() throws Throwable {
String main = "classmerging.ClassWithNativeMethodTest";
Path[] programFiles =
new Path[] {
@@ -676,7 +891,7 @@
}
@Test
- public void testNoIllegalClassAccess() throws Exception {
+ public void testNoIllegalClassAccess() throws Throwable {
String main = "classmerging.SimpleInterfaceAccessTest";
Path[] programFiles =
new Path[] {
@@ -701,7 +916,7 @@
}
@Test
- public void testNoIllegalClassAccessWithAccessModifications() throws Exception {
+ public void testNoIllegalClassAccessWithAccessModifications() throws Throwable {
// If access modifications are allowed then SimpleInterface should be merged into
// SimpleInterfaceImpl.
String main = "classmerging.SimpleInterfaceAccessTest";
@@ -736,7 +951,7 @@
// into an invoke-direct instruction after it gets merged into class C. We should add a test that
// checks that this works with and without inlining of the method B.m().
@Test
- public void testRewritePinnedMethod() throws Exception {
+ public void testRewritePinnedMethod() throws Throwable {
String main = "classmerging.RewritePinnedMethodTest";
Path[] programFiles =
new Path[] {
@@ -759,7 +974,7 @@
}
@Test
- public void testTemplateMethodPattern() throws Exception {
+ public void testTemplateMethodPattern() throws Throwable {
String main = "classmerging.TemplateMethodTest";
Path[] programFiles =
new Path[] {
@@ -774,7 +989,7 @@
}
private CodeInspector runTest(
- String main, Path[] programFiles, Predicate<String> preservedClassNames) throws Exception {
+ String main, Path[] programFiles, Predicate<String> preservedClassNames) throws Throwable {
return runTest(main, programFiles, preservedClassNames, getProguardConfig(EXAMPLE_KEEP));
}
@@ -783,14 +998,14 @@
Path[] programFiles,
Predicate<String> preservedClassNames,
String proguardConfig)
- throws Exception {
+ throws Throwable {
return runTestOnInput(
main, readProgramFiles(programFiles), preservedClassNames, proguardConfig);
}
private CodeInspector runTestOnInput(
String main, AndroidApp input, Predicate<String> preservedClassNames, String proguardConfig)
- throws Exception {
+ throws Throwable {
return runTestOnInput(main, input, preservedClassNames, proguardConfig, this::configure);
}
@@ -800,8 +1015,37 @@
Predicate<String> preservedClassNames,
String proguardConfig,
Consumer<InternalOptions> optionsConsumer)
- throws Exception {
- AndroidApp output = compileWithR8(input, proguardConfig, optionsConsumer);
+ throws Throwable {
+ return runTestOnInput(
+ main,
+ input,
+ preservedClassNames,
+ proguardConfig,
+ optionsConsumer,
+ new VerticalClassMergerDebugTest(main));
+ }
+
+ private CodeInspector runTestOnInput(
+ String main,
+ AndroidApp input,
+ Predicate<String> preservedClassNames,
+ String proguardConfig,
+ Consumer<InternalOptions> optionsConsumer,
+ VerticalClassMergerDebugTest debugTestRunner)
+ throws Throwable {
+ Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
+ R8Command.Builder commandBuilder =
+ ToolHelper.prepareR8CommandBuilder(input)
+ .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+ ToolHelper.allowTestProguardOptions(commandBuilder);
+ AndroidApp output =
+ ToolHelper.runR8(
+ commandBuilder.build(),
+ options -> {
+ optionsConsumer.accept(options);
+ options.proguardMapConsumer =
+ new FileConsumer(proguardMapPath, options.proguardMapConsumer);
+ });
CodeInspector inputInspector = new CodeInspector(input);
CodeInspector outputInspector = new CodeInspector(output);
// Check that all classes in [preservedClassNames] are in fact preserved.
@@ -815,6 +1059,11 @@
}
// Check that the R8-generated code produces the same result as D8-generated code.
assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
+ // Check that we never come across a method that has a name with "$classmerging$" in it during
+ // debugging.
+ if (debugTestRunner != null) {
+ debugTestRunner.test(output, proguardMapPath);
+ }
return outputInspector;
}
@@ -830,4 +1079,82 @@
}
return builder.toString();
}
+
+ private class VerticalClassMergerDebugTest extends DebugTestBase {
+
+ private final String main;
+ private DebugTestRunner runner = null;
+
+ public VerticalClassMergerDebugTest(String main) {
+ this.main = main;
+ }
+
+ public void test(AndroidApp app, Path proguardMapPath) throws Throwable {
+ Path appPath =
+ File.createTempFile("app", ".zip", ClassMergingTest.this.temp.getRoot()).toPath();
+ app.writeToZip(appPath, OutputMode.DexIndexed);
+
+ DexDebugTestConfig config = new DexDebugTestConfig(appPath);
+ config.allowUnprocessedCommands();
+ config.setProguardMap(proguardMapPath);
+
+ this.runner =
+ getDebugTestRunner(
+ config, main, breakpoint(main, "main"), run(), stepIntoUntilNoLongerInApp());
+ this.runner.runBare();
+ }
+
+ private void checkState(DebuggeeState state) {
+ // If a class pkg.A is merged into pkg.B, and a method pkg.A.m() needs to be renamed, then
+ // it will be renamed to pkg.B.m$pkg$A(). Since all tests are in the package "classmerging",
+ // we check that no methods in the debugging state (i.e., after the Proguard map has been
+ // applied) contain "$classmerging$.
+ String qualifiedMethodSignature =
+ state.getClassSignature() + "->" + state.getMethodName() + state.getMethodSignature();
+ boolean holderIsCompanionClass = state.getClassName().endsWith(COMPANION_CLASS_NAME_SUFFIX);
+ if (!holderIsCompanionClass) {
+ assertThat(qualifiedMethodSignature, not(containsString("$classmerging$")));
+ }
+ }
+
+ // Keeps stepping in until it is no longer in a class from the classmerging package.
+ // Then starts stepping out until it is again in the classmerging package.
+ private Command stepIntoUntilNoLongerInApp() {
+ return stepUntil(
+ StepKind.INTO,
+ StepLevel.INSTRUCTION,
+ state -> {
+ if (state.getClassSignature().contains("classmerging")) {
+ checkState(state);
+
+ // Continue stepping into.
+ return false;
+ }
+
+ // Stop stepping into.
+ runner.enqueueCommandFirst(stepOutUntilInApp());
+ return true;
+ });
+ }
+
+ // Keeps stepping out until it is in a class from the classmerging package.
+ // Then starts stepping in until it is no longer in the classmerging package.
+ private Command stepOutUntilInApp() {
+ return stepUntil(
+ StepKind.OUT,
+ StepLevel.INSTRUCTION,
+ state -> {
+ if (state.getClassSignature().contains("classmerging")) {
+ checkState(state);
+
+ // Stop stepping out.
+ runner.enqueueCommandFirst(stepIntoUntilNoLongerInApp());
+ return true;
+ }
+
+ // Continue stepping out.
+ return false;
+ });
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 5707812..e9ef5ed 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -137,6 +137,18 @@
private void runInternal(
DebugTestConfig config, String debuggeeClass, List<JUnit3Wrapper.Command> commands)
throws Throwable {
+ getDebugTestRunner(config, debuggeeClass, commands).runBare();
+ }
+
+ protected DebugTestRunner getDebugTestRunner(
+ DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
+ throws Throwable {
+ return getDebugTestRunner(config, debuggeeClass, Arrays.asList(commands));
+ }
+
+ protected DebugTestRunner getDebugTestRunner(
+ DebugTestConfig config, String debuggeeClass, List<JUnit3Wrapper.Command> commands)
+ throws Throwable {
// Skip test due to unsupported runtime.
Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
ToolHelper.artSupported());
@@ -152,7 +164,7 @@
? null
: ClassNameMapper.mapperFromFile(config.getProguardMap());
- new JUnit3Wrapper(config, debuggeeClass, commands, classNameMapper).runBare();
+ return new JUnit3Wrapper(config, debuggeeClass, commands, classNameMapper);
}
/**
@@ -495,8 +507,14 @@
return t -> inspector.accept(t.debuggeeState.getLocalValues().get(localName));
}
+ protected interface DebugTestRunner {
+ void enqueueCommandFirst(JUnit3Wrapper.Command command);
+
+ void runBare() throws Throwable;
+ }
+
@Ignore("Prevents Gradle from running the wrapper as a test.")
- static class JUnit3Wrapper extends JDWPTestCase {
+ public static class JUnit3Wrapper extends JDWPTestCase implements DebugTestRunner {
private final String debuggeeClassName;
@@ -630,7 +648,7 @@
return null;
}
ClassNamingForNameMapper.MappedRangesOfName ranges =
- classNaming.mappedRangesByName.get(obfuscatedMethodName);
+ classNaming.mappedRangesByRenamedName.get(obfuscatedMethodName);
if (ranges == null) {
return null;
}
@@ -683,7 +701,7 @@
if (range == null) {
return obfuscatedLineNumber;
}
- return range.originalLineFromObfuscated(obfuscatedLineNumber);
+ return range.getOriginalLineNumber(obfuscatedLineNumber);
}
@Override
@@ -694,7 +712,7 @@
return null;
}
ClassNamingForNameMapper.MappedRangesOfName ranges =
- classNaming.mappedRangesByName.get(obfuscatedMethodName);
+ classNaming.mappedRangesByRenamedName.get(obfuscatedMethodName);
if (ranges == null) {
return null;
}
@@ -706,8 +724,7 @@
for (MappedRange range : mappedRanges) {
lines.add(
new SignatureAndLine(
- range.signature.toString(),
- range.originalLineFromObfuscated(obfuscatedLineNumber)));
+ range.signature.toString(), range.getOriginalLineNumber(obfuscatedLineNumber)));
}
return lines;
}
@@ -801,8 +818,10 @@
// Continue stepping until mainLoopStep exits with false.
}
- assertTrue(
- "All commands have NOT been processed for config: " + config, commandsQueue.isEmpty());
+ if (config.mustProcessAllCommands()) {
+ assertTrue(
+ "All commands have NOT been processed for config: " + config, commandsQueue.isEmpty());
+ }
logWriter.println("Finish loop");
}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
index 556e17f..519b580 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
@@ -17,6 +17,7 @@
DEX
}
+ private boolean mustProcessAllCommands = true;
private List<Path> paths = new ArrayList<>();
private Path proguardMap = null;
@@ -47,6 +48,14 @@
return this;
}
+ public boolean mustProcessAllCommands() {
+ return mustProcessAllCommands;
+ }
+
+ public void allowUnprocessedCommands() {
+ mustProcessAllCommands = false;
+ }
+
/** Proguard map that the debuggee has been translated according to, null if not present. */
public Path getProguardMap() {
return proguardMap;
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index 0f8ac65..ce84f51 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -185,7 +185,7 @@
ClassNamingForNameMapper classNaming = mapper.getClassNaming(TEST_PACKAGE + "." + TEST_CLASS);
assertNotNull(classNaming);
- MappedRangesOfName rangesForMain = classNaming.mappedRangesByName.get("main");
+ MappedRangesOfName rangesForMain = classNaming.mappedRangesByRenamedName.get("main");
assertNotNull(rangesForMain);
List<MappedRange> frames = rangesForMain.allRangesForLine(expectedLineNumber);
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index abc7a73..6ae151e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -3,13 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
+import static junit.framework.TestCase.assertEquals;
+
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.beust.jcommander.internal.Lists;
@@ -20,52 +21,60 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
- public Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
+ private static class CompilationResult {
+ AndroidApp app;
+ String proguardMap;
+ }
+
+ private static Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
List<DexEncodedMethod> toShuffle = Lists.newArrayList(methods);
Collections.shuffle(toShuffle);
return new LinkedHashSet<>(toShuffle);
}
- private AndroidApp doRun()
- throws IOException, ProguardRuleParserException, ExecutionException,
- CompilationFailedException {
+ private CompilationResult doRun() throws IOException, CompilationFailedException {
R8Command command =
R8Command.builder()
.addProgramFiles(Paths.get(GMSCORE_V7_DIR, GMSCORE_APK))
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setMinApiLevel(AndroidApiLevel.L.getLevel())
.build();
- return ToolHelper.runR8(
- command,
- options -> {
- // For this test just do random shuffle.
- options.testing.irOrdering = this::shuffle;
- // Only use one thread to process to process in the order decided by the callback.
- options.numberOfThreads = 1;
- // Ignore the missing classes.
- options.ignoreMissingClasses = true;
- });
+ CompilationResult result = new CompilationResult();
+ result.app =
+ ToolHelper.runR8(
+ command,
+ options -> {
+ // For this test just do random shuffle.
+ options.testing.irOrdering = R8GMSCoreDeterministicTest::shuffle;
+ // Only use one thread to process to process in the order decided by the callback.
+ options.numberOfThreads = 1;
+ // Ignore the missing classes.
+ options.ignoreMissingClasses = true;
+ // Store the generated Proguard map.
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> result.proguardMap = proguardMap;
+ });
+ return result;
}
@Test
public void deterministic() throws Exception {
-
// Run two independent compilations.
- AndroidApp app1 = doRun();
- AndroidApp app2 = doRun();
+ CompilationResult result1 = doRun();
+ CompilationResult result2 = doRun();
// Check that the generated bytecode runs through the dex2oat verifier with no errors.
Path combinedInput = temp.getRoot().toPath().resolve("all.jar");
Path oatFile = temp.getRoot().toPath().resolve("all.oat");
- app1.writeToZip(combinedInput, OutputMode.DexIndexed);
+ result1.app.writeToZip(combinedInput, OutputMode.DexIndexed);
ToolHelper.runDex2Oat(combinedInput, oatFile);
// Verify that the result of the two compilations was the same.
- assertIdenticalApplications(app1, app2);
+ assertIdenticalApplications(result1.app, result2.app);
+ assertEquals(result1.proguardMap, result2.proguardMap);
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
index 6adf680..d881185 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.internal;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
import com.android.tools.r8.utils.AndroidApp;
@@ -11,17 +13,33 @@
public class R8GMSCoreV10DeployJarVerificationTest extends GMSCoreDeployJarVerificationTest {
+ private String proguardMap1 = null;
+ private String proguardMap2 = null;
+
@Test
public void buildFromDeployJar() throws Exception {
// TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
- AndroidApp app1 = buildFromDeployJar(
- CompilerUnderTest.R8, CompilationMode.RELEASE,
- GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
- AndroidApp app2 = buildFromDeployJar(
- CompilerUnderTest.R8, CompilationMode.RELEASE,
- GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
+ AndroidApp app1 =
+ buildFromDeployJar(
+ CompilerUnderTest.R8,
+ CompilationMode.RELEASE,
+ GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
+ false,
+ options ->
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+ AndroidApp app2 =
+ buildFromDeployJar(
+ CompilerUnderTest.R8,
+ CompilationMode.RELEASE,
+ GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
+ false,
+ options ->
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> this.proguardMap2 = proguardMap);
// Verify that the result of the two compilations was the same.
assertIdenticalApplications(app1, app2);
+ assertEquals(proguardMap1, proguardMap2);
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
index ae470c6..4374d0e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
+import static org.junit.Assert.assertEquals;
+
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.utils.AndroidApp;
import org.junit.Test;
@@ -10,15 +12,33 @@
public class R8GMSCoreV10TreeShakeJarVerificationTest
extends R8GMSCoreTreeShakeJarVerificationTest {
+ private String proguardMap1 = null;
+ private String proguardMap2 = null;
+
@Test
public void buildAndTreeShakeFromDeployJar() throws Exception {
// TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
- AndroidApp app1 = buildAndTreeShakeFromDeployJar(
- CompilationMode.RELEASE, GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE, null);
- AndroidApp app2 = buildAndTreeShakeFromDeployJar(
- CompilationMode.RELEASE, GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE, null);
+ AndroidApp app1 =
+ buildAndTreeShakeFromDeployJar(
+ CompilationMode.RELEASE,
+ GMSCORE_V10_DIR,
+ false,
+ GMSCORE_V10_MAX_SIZE,
+ options ->
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+ AndroidApp app2 =
+ buildAndTreeShakeFromDeployJar(
+ CompilationMode.RELEASE,
+ GMSCORE_V10_DIR,
+ false,
+ GMSCORE_V10_MAX_SIZE,
+ options ->
+ options.proguardMapConsumer =
+ (proguardMap, handler) -> this.proguardMap2 = proguardMap);
// Verify that the result of the two compilations was the same.
assertIdenticalApplications(app1, app2);
+ assertEquals(proguardMap1, proguardMap2);
}
}
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index 3421cfe..911ceb7 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
@@ -16,10 +17,25 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class Regress69825683Test extends TestBase {
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public Regress69825683Test(Backend backend) {
+ this.backend = backend;
+ }
@Test
public void outerConstructsInner() throws Exception {
@@ -32,9 +48,18 @@
"}",
"-dontobfuscate"),
Origin.unknown());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
- CodeInspector inspector = new CodeInspector(app);
+ CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
List<FoundClassSubject> classes = inspector.allClasses();
// Check that the synthetic class is still present.
@@ -51,7 +76,9 @@
String innerName = innerClass.getCanonicalName();
int index = innerName.lastIndexOf('.');
innerName = innerName.substring(0, index) + "$" + innerName.substring(index + 1);
- assertTrue(runOnArt(app, mainClass).startsWith(innerName));
+ assertTrue(
+ (backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass))
+ .startsWith(innerName));
}
@Test
@@ -65,9 +92,18 @@
"}",
"-dontobfuscate"),
Origin.unknown());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
- CodeInspector inspector = new CodeInspector(app);
+ CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
List<FoundClassSubject> classes = inspector.allClasses();
// Check that the synthetic class is still present.
@@ -79,6 +115,8 @@
.count());
// Run code to check that the constructor with synthetic class as argument is present.
- assertTrue(runOnArt(app, mainClass).startsWith(mainClass.getCanonicalName()));
+ assertTrue(
+ (backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass))
+ .startsWith(mainClass.getCanonicalName()));
}
}
diff --git a/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java b/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java
index f40ecfd..cc356c3 100644
--- a/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java
@@ -6,15 +6,32 @@
import static junit.framework.TestCase.assertEquals;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class Regress71604169Test extends TestBase {
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public Regress71604169Test(Backend backend) {
+ this.backend = backend;
+ }
+
@Test
public void test() throws Exception {
R8Command.Builder builder = R8Command.builder();
@@ -28,7 +45,20 @@
builder.addProguardConfiguration(
ImmutableList.of(keepMainProguardConfiguration(mainClass, true, false)), Origin.unknown());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- assertEquals("Hello, world!", runOnArt(ToolHelper.runR8(builder.build()), mainClass));
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
+ assertEquals(
+ "Hello, world!",
+ backend == Backend.DEX
+ ? runOnArt(ToolHelper.runR8(builder.build()), mainClass)
+ : runOnJava(ToolHelper.runR8(builder.build()), mainClass));
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index cba8686..ad67919 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -14,12 +14,15 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.PrintStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Assert;
@@ -27,8 +30,28 @@
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+@RunWith(Parameterized.class)
public class TreeShakingSpecificTest {
+ enum Backend {
+ DEX,
+ CF
+ }
+
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public TreeShakingSpecificTest(Backend backend) {
+ this.backend = backend;
+ }
+
private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
@Rule
@@ -37,18 +60,28 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
+ private void finishBuild(R8Command.Builder builder, Path out, String test) throws IOException {
+ Path input;
+ if (backend == Backend.DEX) {
+ builder.setOutput(out, OutputMode.DexIndexed);
+ input = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
+ } else {
+ builder.setOutput(out, OutputMode.ClassFile);
+ input = Paths.get(EXAMPLES_BUILD_DIR, test + ".jar");
+ }
+ ToolHelper.getAppBuilder(builder).addProgramFiles(input);
+ }
+
@Test
public void testIgnoreWarnings() throws Exception {
// Generate R8 processed version without library option.
Path out = temp.getRoot().toPath();
String test = "shaking2";
- Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
Path ignoreWarnings = Paths.get(VALID_PROGUARD_DIR, "ignorewarnings.flags");
R8Command.Builder builder = R8Command.builder()
- .setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(keepRules, ignoreWarnings);
- ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+ finishBuild(builder, out, test);
R8.run(builder.build());
}
@@ -57,7 +90,6 @@
// Generate R8 processed version without library option.
Path out = temp.getRoot().toPath();
String test = "shaking2";
- Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
DiagnosticsHandler handler = new DiagnosticsHandler() {
@Override
@@ -68,9 +100,8 @@
}
};
R8Command.Builder builder = R8Command.builder(handler)
- .setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(keepRules);
- ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+ finishBuild(builder, out, test);
R8.run(builder.build());
}
@@ -79,7 +110,6 @@
// Generate R8 processed version without library option.
String test = "shaking1";
Path out = temp.getRoot().toPath();
- Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
// Create a flags file in temp dir requesting dump of the mapping.
@@ -90,10 +120,15 @@
}
R8Command.Builder builder = R8Command.builder()
- .setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(keepRules, printMapping);
- ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
// Turn off inlining, as we want the mapping that is printed to be stable.
+ finishBuild(builder, out, test);
+ if (backend == Backend.DEX) {
+ builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
Path outputmapping = out.resolve("mapping.txt");
@@ -102,8 +137,15 @@
Stream.of(new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8).split("\n"))
.filter(line -> !line.startsWith("#"))
.collect(Collectors.joining("\n"));
- String refMapping = new String(Files.readAllBytes(
- Paths.get(EXAMPLES_DIR, "shaking1", "print-mapping.ref")), StandardCharsets.UTF_8);
+ // For the CF backend we treat ConstString/LDC as a (potentially always) throwing instruction,
+ // as opposed to the DEX backend where it's throwing only if its string is ill-formed.
+ // When ConstString is throwing we preserve its position which makes it show up in the
+ // the output Proguard map. That's why the reference CF map is different from the DEX one.
+ String mapping_ref = backend == Backend.CF ? "print-mapping-cf.ref" : "print-mapping.ref";
+ String refMapping =
+ new String(
+ Files.readAllBytes(Paths.get(EXAMPLES_DIR, "shaking1", mapping_ref)),
+ StandardCharsets.UTF_8);
Assert.assertEquals(sorted(refMapping), sorted(actualMapping));
}
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 eab5197..d753632 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
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
@@ -19,18 +20,43 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class DefaultMethodsTest extends TestBase {
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public DefaultMethodsTest(Backend backend) {
+ this.backend = backend;
+ }
+
private void runTest(List<String> additionalKeepRules, Consumer<CodeInspector> inspection)
throws Exception {
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(InterfaceWithDefaultMethods.class));
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(ClassImplementingInterface.class));
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
+ if (backend == Backend.DEX) {
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ int apiLevel = AndroidApiLevel.O.getLevel();
+ builder.setMinApiLevel(apiLevel);
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(apiLevel));
+ } else {
+ assert backend == Backend.CF;
+ builder.setProgramConsumer(ClassFileConsumer.emptyConsumer());
+ builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
// Always keep main in the test class, so the output never becomes empty.
builder.addProguardConfiguration(ImmutableList.of(
"-keep class " + TestClass.class.getCanonicalName() + "{",
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index 8d38aee..b3e6367 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -6,14 +6,11 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.InvokeInterface;
-import com.android.tools.r8.code.InvokeVirtual;
-import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.invokesuper.Consumer;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.origin.Origin;
@@ -24,11 +21,32 @@
import com.android.tools.r8.shaking.proxy.testclasses.TestClass;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
+import java.util.function.Predicate;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class ProxiesTest extends TestBase {
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public ProxiesTest(Backend backend) {
+ this.backend = backend;
+ }
private void runTest(List<String> additionalKeepRules, Consumer<CodeInspector> inspection,
String expectedResult)
@@ -49,52 +67,91 @@
Origin.unknown()
);
builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
- builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ if (backend == Backend.DEX) {
+ builder
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ } else {
+ assert backend == Backend.CF;
+ builder
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+ }
AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableDevirtualization = false);
- inspection.accept(new CodeInspector(app));
- assertEquals(expectedResult, runOnArt(app, mainClass));
+ inspection.accept(new CodeInspector(app, o -> o.enableCfFrontend = true));
+ String result = backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass);
+ if (ToolHelper.isWindows()) {
+ result = result.replace(System.lineSeparator(), "\n");
+ }
+ assertEquals(expectedResult, result);
}
- private int countInstructionInX(CodeInspector inspector, Class<? extends Instruction> invoke) {
+ private int countInstructionInX(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForX =
new MethodSignature("x", "void", ImmutableList.of(BaseInterface.class.getCanonicalName()));
- DexCode x = inspector.clazz(Main.class).method(signatureForX).getMethod().getCode().asDexCode();
- return (int) filterInstructionKind(x, invoke).count();
+ MethodSubject method = inspector.clazz(Main.class).method(signatureForX);
+ assert method instanceof FoundMethodSubject;
+ FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+ return (int) Streams.stream(foundMethod.iterateInstructions(invoke)).count();
}
- private int countInstructionInY(CodeInspector inspector, Class<? extends Instruction> invoke) {
+ private int countInstructionInY(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForY =
new MethodSignature("y", "void", ImmutableList.of(SubInterface.class.getCanonicalName()));
- DexCode y = inspector.clazz(Main.class).method(signatureForY).getMethod().getCode().asDexCode();
- return (int) filterInstructionKind(y, invoke)
- .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
- .count();
+ MethodSubject method = inspector.clazz(Main.class).method(signatureForY);
+ assert method instanceof FoundMethodSubject;
+ FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+ return (int)
+ Streams.stream(foundMethod.iterateInstructions(invoke))
+ .filter(
+ instruction -> {
+ InvokeInstructionSubject invokeInstruction =
+ (InvokeInstructionSubject) instruction;
+ return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
+ })
+ .count();
}
- private int countInstructionInZ(CodeInspector inspector, Class<? extends Instruction> invoke) {
+ private int countInstructionInZ(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(TestClass.class.getCanonicalName()));
- DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
- return (int) filterInstructionKind(z, invoke)
- .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
- .count();
+ MethodSubject method = inspector.clazz(Main.class).method(signatureForZ);
+ assert method instanceof FoundMethodSubject;
+ FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+ return (int)
+ Streams.stream(foundMethod.iterateInstructions(invoke))
+ .filter(
+ instruction -> {
+ InvokeInstructionSubject invokeInstruction =
+ (InvokeInstructionSubject) instruction;
+ return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
+ })
+ .count();
}
private int countInstructionInZSubClass(
- CodeInspector inspector, Class<? extends Instruction> invoke) {
+ CodeInspector inspector, Predicate<InstructionSubject> invoke) {
MethodSignature signatureForZ =
new MethodSignature("z", "void", ImmutableList.of(SubClass.class.getCanonicalName()));
- DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
- return (int) filterInstructionKind(z, invoke)
- .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
- .count();
+ MethodSubject method = inspector.clazz(Main.class).method(signatureForZ);
+ assert method instanceof FoundMethodSubject;
+ FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+ return (int)
+ Streams.stream(foundMethod.iterateInstructions(invoke))
+ .filter(
+ instruction -> {
+ InvokeInstructionSubject invokeInstruction =
+ (InvokeInstructionSubject) instruction;
+ return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
+ })
+ .count();
}
private void noInterfaceKept(CodeInspector inspector) {
// Indirectly assert that method is inlined into x, y and z.
- assertEquals(1, countInstructionInX(inspector, InvokeInterface.class));
- assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
- assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
+ assertEquals(1, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(1, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
@@ -106,11 +163,11 @@
private void baseInterfaceKept(CodeInspector inspector) {
// Indirectly assert that method is not inlined into x.
- assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
+ assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
// Indirectly assert that method is inlined into y and z.
- assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
- assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
- assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
+ assertEquals(1, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
+ assertEquals(1, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
@@ -126,11 +183,11 @@
private void subInterfaceKept(CodeInspector inspector) {
// Indirectly assert that method is not inlined into x or y.
- assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
- assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
+ assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
// Indirectly assert that method is inlined into z.
- assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
- assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
+ assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
+ assertEquals(1, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
@@ -148,10 +205,10 @@
private void classKept(CodeInspector inspector) {
// Indirectly assert that method is not inlined into x, y or z.
- assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
- assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
- assertEquals(3, countInstructionInZ(inspector, InvokeVirtual.class));
- assertEquals(3, countInstructionInZSubClass(inspector, InvokeVirtual.class));
+ assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+ assertEquals(3, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
+ assertEquals(3, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index 147b1b4..d12f41a 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -13,6 +13,9 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
@@ -20,17 +23,45 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class ForceInlineTest extends TestBase {
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public ForceInlineTest(Backend backend) {
+ this.backend = backend;
+ }
private CodeInspector runTest(List<String> proguardConfiguration) throws Exception {
+ ProgramConsumer programConsumer;
+ Path library;
+ if (backend == Backend.DEX) {
+ programConsumer = DexIndexedConsumer.emptyConsumer();
+ library = ToolHelper.getDefaultAndroidJar();
+ } else {
+ assert backend == Backend.CF;
+ programConsumer = ClassFileConsumer.emptyConsumer();
+ library = ToolHelper.getJava8RuntimeJar();
+ }
R8Command.Builder builder =
- ToolHelper.prepareR8CommandBuilder(readClasses(Main.class, A.class, B.class, C.class));
+ ToolHelper.prepareR8CommandBuilder(
+ readClasses(Main.class, A.class, B.class, C.class), programConsumer)
+ .addLibraryFiles(library);
ToolHelper.allowTestProguardOptions(builder);
builder.addProguardConfiguration(proguardConfiguration, Origin.unknown());
- return new CodeInspector(ToolHelper.runR8(builder.build()));
+ return new CodeInspector(ToolHelper.runR8(builder.build(), o -> o.enableCfFrontend = true));
}
@Test