Merge "Add test for signature annotation with vertical class merging"
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 00157f5..3985cd7 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -5,10 +5,12 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.FlagFile;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.nio.file.Files;
@@ -21,6 +23,15 @@
public class D8CommandParser extends BaseCompilerCommandParser {
+ private static final Set<String> OPTIONS_WITH_PARAMETER =
+ ImmutableSet.of(
+ "--output",
+ "--lib",
+ "--classpath",
+ "--min-api",
+ "--main-dex-list",
+ "--main-dex-list-output");
+
private static final String APK_EXTENSION = ".apk";
private static final String JAR_EXTENSION = ".jar";
private static final String ZIP_EXTENSION = ".zip";
@@ -106,13 +117,16 @@
" # <file> must be an existing directory or a zip file.",
" --lib <file> # Add <file> as a library resource.",
" --classpath <file> # Add <file> as a classpath resource.",
- " --min-api # Minimum Android API level compatibility",
+ " --min-api <number> # Minimum Android API level compatibility, default: "
+ + AndroidApiLevel.getDefault().getLevel()
+ + ".",
" --intermediate # Compile an intermediate result intended for later",
" # merging.",
" --file-per-class # Produce a separate dex file per input class",
" --no-desugaring # Force disable desugaring.",
" --main-dex-list <file> # List of classes to place in the primary dex file.",
- " --main-dex-list-output <file> # Output resulting main dex list in <file>.",
+ " --main-dex-list-output <file>",
+ " # Output resulting main dex list in <file>.",
" --version # Print the version of d8.",
" --help # Print this message."));
@@ -153,6 +167,16 @@
String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
+ String nextArg = null;
+ if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+ if (++i < expandedArgs.length) {
+ nextArg = expandedArgs[i];
+ } else {
+ builder.error(
+ new StringDiagnostic("Missing parameter for " + expandedArgs[i - 1] + ".", origin));
+ break;
+ }
+ }
if (arg.length() == 0) {
continue;
} else if (arg.equals("--help")) {
@@ -176,19 +200,18 @@
} else if (arg.equals("--file-per-class")) {
outputMode = OutputMode.DexFilePerClass;
} else if (arg.equals("--output")) {
- String output = expandedArgs[++i];
if (outputPath != null) {
builder.error(
new StringDiagnostic(
- "Cannot output both to '" + outputPath.toString() + "' and '" + output + "'",
+ "Cannot output both to '" + outputPath.toString() + "' and '" + nextArg + "'",
origin));
continue;
}
- outputPath = Paths.get(output);
+ outputPath = Paths.get(nextArg);
} else if (arg.equals("--lib")) {
- builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
+ builder.addLibraryFiles(Paths.get(nextArg));
} else if (arg.equals("--classpath")) {
- Path file = Paths.get(expandedArgs[++i]);
+ Path file = Paths.get(nextArg);
try {
if (!Files.exists(file)) {
throw new NoSuchFileException(file.toString());
@@ -206,17 +229,16 @@
builder.error(new ExceptionDiagnostic(e, new PathOrigin(file)));
}
} else if (arg.equals("--main-dex-list")) {
- builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
+ builder.addMainDexListFiles(Paths.get(nextArg));
} else if (arg.equals("--main-dex-list-output")) {
- builder.setMainDexListOutputPath(Paths.get(expandedArgs[++i]));
+ builder.setMainDexListOutputPath(Paths.get(nextArg));
} else if (arg.equals("--optimize-multidex-for-linearalloc")) {
builder.setOptimizeMultidexForLinearAlloc(true);
} else if (arg.equals("--min-api")) {
- String minApiString = expandedArgs[++i];
if (hasDefinedApiLevel) {
builder.error(new StringDiagnostic("Cannot set multiple --min-api options", origin));
} else {
- parseMinApi(builder, minApiString, origin);
+ parseMinApi(builder, nextArg, origin);
hasDefinedApiLevel = true;
}
} else if (arg.equals("--intermediate")) {
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 580b068..b91739a 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -4,14 +4,28 @@
package com.android.tools.r8;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.FlagFile;
import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.Set;
public class R8CommandParser extends BaseCompilerCommandParser {
+ private static final Set<String> OPTIONS_WITH_PARAMETER =
+ ImmutableSet.of(
+ "--output",
+ "--lib",
+ "--min-api",
+ "--main-dex-rules",
+ "--main-dex-list",
+ "--main-dex-list-output",
+ "--pg-conf",
+ "--pg-map-output");
+
public static void main(String[] args) throws CompilationFailedException {
R8Command command = parse(args, Origin.root()).build();
if (command.isPrintHelp()) {
@@ -45,7 +59,9 @@
" --output <file> # Output result in <file>.",
" # <file> must be an existing directory or a zip file.",
" --lib <file> # Add <file> as a library resource.",
- " --min-api # Minimum Android API level compatibility.",
+ " --min-api <number> # Minimum Android API level compatibility, default: "
+ + AndroidApiLevel.getDefault().getLevel()
+ + ".",
" --pg-conf <file> # Proguard configuration <file>.",
" --pg-map-output <file> # Output the resulting name and line mapping to <file>.",
" --no-tree-shaking # Force disable tree shaking of unreachable classes.",
@@ -55,7 +71,8 @@
" --main-dex-rules <file> # Proguard keep rules for classes to place in the",
" # primary dex file.",
" --main-dex-list <file> # List of classes to place in the primary dex file.",
- " --main-dex-list-output <file> # Output the full main-dex list in <file>.",
+ " --main-dex-list-output <file> ",
+ " # Output the full main-dex list in <file>.",
" --version # Print the version of r8.",
" --help # Print this message."));
/**
@@ -102,6 +119,17 @@
String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
for (int i = 0; i < expandedArgs.length; i++) {
String arg = expandedArgs[i].trim();
+ String nextArg = null;
+ if (OPTIONS_WITH_PARAMETER.contains(arg)) {
+ if (++i < expandedArgs.length) {
+ nextArg = expandedArgs[i];
+ } else {
+ builder.error(
+ new StringDiagnostic(
+ "Missing parameter for " + expandedArgs[i - 1] + ".", argsOrigin));
+ break;
+ }
+ }
if (arg.length() == 0) {
continue;
} else if (arg.equals("--help")) {
@@ -137,26 +165,24 @@
}
state.outputMode = OutputMode.ClassFile;
} else if (arg.equals("--output")) {
- String outputPath = expandedArgs[++i];
if (state.outputPath != null) {
builder.error(
new StringDiagnostic(
"Cannot output both to '"
+ state.outputPath.toString()
+ "' and '"
- + outputPath
+ + nextArg
+ "'",
argsOrigin));
}
- state.outputPath = Paths.get(outputPath);
+ state.outputPath = Paths.get(nextArg);
} else if (arg.equals("--lib")) {
- builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
+ builder.addLibraryFiles(Paths.get(nextArg));
} else if (arg.equals("--min-api")) {
- String minApiString = expandedArgs[++i];
if (state.hasDefinedApiLevel) {
builder.error(new StringDiagnostic("Cannot set multiple --min-api options", argsOrigin));
} else {
- parseMinApi(builder, minApiString, argsOrigin);
+ parseMinApi(builder, nextArg, argsOrigin);
state.hasDefinedApiLevel = true;
}
} else if (arg.equals("--no-tree-shaking")) {
@@ -166,17 +192,17 @@
} else if (arg.equals("--no-desugaring")) {
builder.setDisableDesugaring(true);
} else if (arg.equals("--main-dex-rules")) {
- builder.addMainDexRulesFiles(Paths.get(expandedArgs[++i]));
+ builder.addMainDexRulesFiles(Paths.get(nextArg));
} else if (arg.equals("--main-dex-list")) {
- builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
+ builder.addMainDexListFiles(Paths.get(nextArg));
} else if (arg.equals("--main-dex-list-output")) {
- builder.setMainDexListOutputPath(Paths.get(expandedArgs[++i]));
+ builder.setMainDexListOutputPath(Paths.get(nextArg));
} else if (arg.equals("--optimize-multidex-for-linearalloc")) {
builder.setOptimizeMultidexForLinearAlloc(true);
} else if (arg.equals("--pg-conf")) {
- builder.addProguardConfigurationFiles(Paths.get(expandedArgs[++i]));
+ builder.addProguardConfigurationFiles(Paths.get(nextArg));
} else if (arg.equals("--pg-map-output")) {
- builder.setProguardMapOutputPath(Paths.get(expandedArgs[++i]));
+ builder.setProguardMapOutputPath(Paths.get(nextArg));
} else if (arg.equals("--no-data-resources")) {
state.includeDataResources = false;
} else {
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 718ce78..b643be3 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -19,9 +19,12 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
+import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -29,8 +32,9 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.stream.Collectors;
-class InterfaceMethodNameMinifier {
+public class InterfaceMethodNameMinifier {
private final AppInfoWithLiveness appInfo;
private final Set<DexCallSite> desugaredCallSites;
@@ -68,6 +72,10 @@
this.options = options;
}
+ public Comparator<Wrapper<DexMethod>> createDefaultInterfaceMethodOrdering() {
+ return (a, b) -> globalStateMap.get(b).size() - globalStateMap.get(a).size();
+ }
+
Map<DexCallSite, DexString> getCallSiteRenamings() {
return callSiteRenamings;
}
@@ -75,7 +83,6 @@
private void reserveNamesInInterfaces() {
for (DexType type : DexType.allInterfaces(appInfo.dexItemFactory)) {
assert type.isInterface();
- frontierState.put(type, type);
frontierState.allocateNamingStateAndReserve(type, type, null);
}
}
@@ -95,7 +102,7 @@
assert clazz.isInterface();
Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(type);
for (DexEncodedMethod method : shuffleMethods(clazz.methods(), options)) {
- addStatesToGlobalMapForMethod(method, collectedStates, type);
+ addStatesToGlobalMapForMethod(method.method, collectedStates, type);
}
}
}
@@ -130,7 +137,7 @@
DexType iface = method.method.holder;
assert iface.isInterface();
Set<NamingState<DexProto, ?>> collectedStates = getReachableStates(iface);
- addStatesToGlobalMapForMethod(method, collectedStates, iface);
+ addStatesToGlobalMapForMethod(method.method, collectedStates, iface);
callSiteMethods.add(equivalence.wrap(method.method));
}
if (callSiteMethods.size() > 1) {
@@ -156,31 +163,35 @@
timing.end();
// Go over every method and assign a name.
timing.begin("Allocate names");
+
// Sort the methods by the number of dependent states, so that we use short names for methods
// that are referenced in many places.
- List<Wrapper<DexMethod>> methods = new ArrayList<>(globalStateMap.keySet());
- methods.sort((a, b) -> globalStateMap.get(b).size() - globalStateMap.get(a).size());
- for (Wrapper<DexMethod> key : methods) {
- if (!unificationParent.getOrDefault(key, key).equals(key)) {
- continue;
- }
- List<MethodNamingState> collectedStates = new ArrayList<>();
- Set<DexMethod> sourceMethods = Sets.newIdentityHashSet();
- for (Wrapper<DexMethod> k : unification.getOrDefault(key, Collections.singleton(key))) {
- DexMethod unifiedMethod = k.get();
- assert unifiedMethod != null;
- sourceMethods.addAll(sourceMethodsMap.get(k));
- for (NamingState<DexProto, ?> namingState : globalStateMap.get(k)) {
- collectedStates.add(
- new MethodNamingState(namingState, unifiedMethod.name, unifiedMethod.proto));
- }
- }
- DexMethod method = key.get();
- assert method != null;
- MethodNamingState originState =
- new MethodNamingState(originStates.get(key), method.name, method.proto);
- assignNameForInterfaceMethodInAllStates(collectedStates, sourceMethods, originState);
+ List<Wrapper<DexMethod>> interfaceMethods =
+ globalStateMap.keySet().stream()
+ .filter(wrapper -> unificationParent.getOrDefault(wrapper, wrapper).equals(wrapper))
+ .sorted(options.testing.minifier.createInterfaceMethodOrdering(this))
+ .collect(Collectors.toList());
+
+ // Propagate reserved names to all states.
+ List<Wrapper<DexMethod>> reservedInterfaceMethods =
+ interfaceMethods.stream()
+ .filter(wrapper -> anyIsReserved(wrapper, unification))
+ .collect(Collectors.toList());
+ for (Wrapper<DexMethod> key : reservedInterfaceMethods) {
+ propagateReservedNames(key, unification);
}
+
+ // Verify that there is no more to propagate.
+ assert reservedInterfaceMethods.stream()
+ .noneMatch(key -> propagateReservedNames(key, unification));
+
+ // Assign names to unreserved interface methods.
+ for (Wrapper<DexMethod> key : interfaceMethods) {
+ if (!reservedInterfaceMethods.contains(key)) {
+ assignNameToInterfaceMethod(key, unification);
+ }
+ }
+
for (Entry<DexCallSite, DexMethod> entry : callSites.entrySet()) {
DexMethod method = entry.getValue();
DexString renamed = minifierState.getRenaming(method);
@@ -195,17 +206,59 @@
timing.end();
}
+ private boolean propagateReservedNames(
+ Wrapper<DexMethod> key, Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification) {
+ Set<Wrapper<DexMethod>> unifiedMethods =
+ unification.getOrDefault(key, Collections.singleton(key));
+ boolean changed = false;
+ for (Wrapper<DexMethod> wrapper : unifiedMethods) {
+ DexMethod unifiedMethod = wrapper.get();
+ assert unifiedMethod != null;
+ for (NamingState<DexProto, ?> namingState : globalStateMap.get(wrapper)) {
+ if (!namingState.isReserved(unifiedMethod.name, unifiedMethod.proto)) {
+ namingState.reserveName(unifiedMethod.name, unifiedMethod.proto);
+ changed = true;
+ }
+ }
+ }
+ return changed;
+ }
+
+ private void assignNameToInterfaceMethod(
+ Wrapper<DexMethod> key, Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification) {
+ List<MethodNamingState> collectedStates = new ArrayList<>();
+ Set<DexMethod> sourceMethods = Sets.newIdentityHashSet();
+ for (Wrapper<DexMethod> k : unification.getOrDefault(key, Collections.singleton(key))) {
+ DexMethod unifiedMethod = k.get();
+ assert unifiedMethod != null;
+ sourceMethods.addAll(sourceMethodsMap.get(k));
+ for (NamingState<DexProto, ?> namingState : globalStateMap.get(k)) {
+ collectedStates.add(
+ new MethodNamingState(namingState, unifiedMethod.name, unifiedMethod.proto));
+ }
+ }
+
+ DexMethod method = key.get();
+ assert method != null;
+
+ Set<String> loggingFilter = options.extensiveInterfaceMethodMinifierLoggingFilter;
+ if (!loggingFilter.isEmpty()) {
+ if (sourceMethods.stream().map(DexMethod::toSourceString).anyMatch(loggingFilter::contains)) {
+ print(method, sourceMethods, collectedStates, System.out);
+ }
+ }
+
+ MethodNamingState originState =
+ new MethodNamingState(originStates.get(key), method.name, method.proto);
+ assignNameForInterfaceMethodInAllStates(collectedStates, sourceMethods, originState);
+ }
+
private void assignNameForInterfaceMethodInAllStates(
List<MethodNamingState> collectedStates,
Set<DexMethod> sourceMethods,
MethodNamingState originState) {
- if (anyIsReserved(collectedStates)) {
- // This method's name is reserved in at least one naming state, so reserve it everywhere.
- for (MethodNamingState state : collectedStates) {
- state.reserveName();
- }
- return;
- }
+ assert !anyIsReserved(collectedStates);
+
// We use the origin state to allocate a name here so that we can reuse names between different
// unrelated interfaces. This saves some space. The alternative would be to use a global state
// for allocating names, which would save the work to search here.
@@ -213,6 +266,7 @@
DexString candidate;
do {
candidate = originState.assignNewName();
+
// If the state returns the same candidate for two consecutive trials, it should be this case:
// 1) an interface method with the same signature (name, param) but different return type
// has been already renamed; and 2) -useuniqueclassmembernames is set.
@@ -240,17 +294,30 @@
}
private void addStatesToGlobalMapForMethod(
- DexEncodedMethod method,
- Set<NamingState<DexProto, ?>> collectedStates,
- DexType originInterface) {
- Wrapper<DexMethod> key = equivalence.wrap(method.method);
- Set<NamingState<DexProto, ?>> stateSet =
- globalStateMap.computeIfAbsent(key, k -> new HashSet<>());
- stateSet.addAll(collectedStates);
- sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method.method);
+ DexMethod method, Set<NamingState<DexProto, ?>> collectedStates, DexType originInterface) {
+ Wrapper<DexMethod> key = equivalence.wrap(method);
+ globalStateMap.computeIfAbsent(key, k -> new HashSet<>()).addAll(collectedStates);
+ sourceMethodsMap.computeIfAbsent(key, k -> new HashSet<>()).add(method);
originStates.putIfAbsent(key, minifierState.getState(originInterface));
}
+ private boolean anyIsReserved(
+ Wrapper<DexMethod> key, Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unification) {
+ Set<Wrapper<DexMethod>> unifiedMethods =
+ unification.getOrDefault(key, Collections.singleton(key));
+ for (Wrapper<DexMethod> wrapper : unifiedMethods) {
+ DexMethod unifiedMethod = wrapper.get();
+ assert unifiedMethod != null;
+
+ for (NamingState<DexProto, ?> namingState : globalStateMap.get(wrapper)) {
+ if (namingState.isReserved(unifiedMethod.name, unifiedMethod.proto)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private boolean anyIsReserved(List<MethodNamingState> collectedStates) {
DexString name = collectedStates.get(0).getName();
Map<DexProto, Boolean> globalStateCache = new IdentityHashMap<>();
@@ -268,17 +335,23 @@
}
private Set<NamingState<DexProto, ?>> getReachableStates(DexType type) {
- Set<DexType> interfaces = Sets.newIdentityHashSet();
- interfaces.add(type);
- collectSuperInterfaces(type, interfaces);
- collectSubInterfaces(type, interfaces);
+ if (minifierState.useUniqueMemberNames()) {
+ return ImmutableSet.of(minifierState.globalState());
+ }
+
+ Set<DexType> reachableInterfaces = Sets.newIdentityHashSet();
+ reachableInterfaces.add(type);
+ collectSuperInterfaces(type, reachableInterfaces);
+ collectSubInterfaces(type, reachableInterfaces);
+
Set<NamingState<DexProto, ?>> reachableStates = new HashSet<>();
- for (DexType iface : interfaces) {
- // Add the interface itself
- reachableStates.add(minifierState.getState(iface));
+ for (DexType reachableInterface : reachableInterfaces) {
+ // Add the interface itself.
+ reachableStates.add(minifierState.getState(reachableInterface));
+
// And the frontiers that correspond to the classes that implement the interface.
- for (DexType t : iface.allImplementsSubtypes()) {
- NamingState<DexProto, ?> state = minifierState.getState(frontierState.get(t));
+ for (DexType frontier : reachableInterface.allImplementsSubtypes()) {
+ NamingState<DexProto, ?> state = minifierState.getState(frontierState.get(frontier));
assert state != null;
reachableStates.add(state);
}
@@ -307,4 +380,21 @@
}
}
}
+
+ private void print(
+ DexMethod method,
+ Set<DexMethod> sourceMethods,
+ List<MethodNamingState> collectedStates,
+ PrintStream out) {
+ out.println("-----------------------------------------------------------------------");
+ out.println("assignNameToInterfaceMethod(`" + method.toSourceString() + "`)");
+ out.println("-----------------------------------------------------------------------");
+ out.println("Source methods:");
+ for (DexMethod sourceMethod : sourceMethods) {
+ out.println(" " + sourceMethod.toSourceString());
+ }
+ out.println("States:");
+ collectedStates.forEach(state -> state.print(" ", minifierState::getStateKey, out));
+ out.println();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 8117dd5..419bfbb 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -69,6 +69,14 @@
return useUniqueMemberNames ? globalState : states.get(type);
}
+ DexType getStateKey(NamingState<StateType, ?> state) {
+ return states.inverse().get(state);
+ }
+
+ NamingState<StateType, ?> globalState() {
+ return globalState;
+ }
+
boolean isReservedInGlobalState(DexString name, StateType state) {
return globalState.isReserved(name, state);
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 4b67208..8864271 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -19,6 +19,7 @@
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableMap;
+import java.io.PrintStream;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -132,8 +133,7 @@
// Phase 1: Reserve all the names that need to be kept and allocate linked state in the
// library part.
timing.begin("Phase 1");
- reserveNamesInClasses(
- appInfo.dexItemFactory.objectType, appInfo.dexItemFactory.objectType, null);
+ reserveNamesInClasses();
timing.end();
// Phase 2: Reserve all the names that are required for interfaces, and then assign names to
// interface methods. These are assigned by finding a name that is free in all naming
@@ -195,30 +195,24 @@
}
}
+ private void reserveNamesInClasses() {
+ reserveNamesInClasses(
+ appInfo.dexItemFactory.objectType, appInfo.dexItemFactory.objectType, null);
+ }
+
private void reserveNamesInClasses(
DexType type, DexType libraryFrontier, NamingState<DexProto, ?> parent) {
assert !type.isInterface();
NamingState<DexProto, ?> state =
frontierState.allocateNamingStateAndReserve(type, libraryFrontier, parent);
+
// If this is a library class (or effectively a library class as it is missing) move the
// frontier forward.
DexClass holder = appInfo.definitionFor(type);
- type.forAllExtendsSubtypes(
- subtype -> {
- assert !subtype.isInterface();
- reserveNamesInClasses(
- subtype,
- holder == null || holder.isLibraryClass() ? subtype : libraryFrontier,
- state);
- });
- }
-
- private void reserveNamesForMethod(
- DexEncodedMethod encodedMethod, boolean keepAll, NamingState<DexProto, ?> state) {
- DexMethod method = encodedMethod.method;
- if (keepAll || rootSet.noObfuscation.contains(method)) {
- state.reserveName(method.name, method.proto);
- globalState.reserveName(method.name, method.proto);
+ for (DexType subtype : type.allExtendsSubtypes()) {
+ assert !subtype.isInterface();
+ reserveNamesInClasses(
+ subtype, holder == null || holder.isLibraryClass() ? subtype : libraryFrontier, state);
}
}
@@ -227,12 +221,14 @@
private final Map<DexType, DexType> frontiers = new IdentityHashMap<>();
NamingState<DexProto, ?> allocateNamingStateAndReserve(
- DexType type, DexType libraryFrontier, NamingState<DexProto, ?> parent) {
- frontiers.put(type, libraryFrontier);
+ DexType type, DexType frontier, NamingState<DexProto, ?> parent) {
+ if (frontier != type) {
+ frontiers.put(type, frontier);
+ }
NamingState<DexProto, ?> state =
computeStateIfAbsent(
- libraryFrontier,
+ frontier,
ignore ->
parent == null
? NamingState.createRoot(
@@ -246,18 +242,28 @@
if (holder != null) {
boolean keepAll = holder.isLibraryClass() || holder.accessFlags.isAnnotation();
for (DexEncodedMethod method : shuffleMethods(holder.methods(), options)) {
- reserveNamesForMethod(method, keepAll, state);
+ // TODO(christofferqa): Wouldn't it be sufficient only to reserve names for non-private
+ // methods?
+ if (keepAll || rootSet.noObfuscation.contains(method.method)) {
+ reserveNamesForMethod(method.method, state);
+ }
}
}
return state;
}
+ private void reserveNamesForMethod(DexMethod method, NamingState<DexProto, ?> state) {
+ state.reserveName(method.name, method.proto);
+ globalState.reserveName(method.name, method.proto);
+ }
+
public DexType get(DexType type) {
- return frontiers.get(type);
+ return frontiers.getOrDefault(type, type);
}
public DexType put(DexType type, DexType frontier) {
+ assert frontier != type;
return frontiers.put(type, frontier);
}
}
@@ -307,6 +313,19 @@
DexProto getProto() {
return proto;
}
+
+ void print(
+ String indentation,
+ Function<NamingState<DexProto, ?>, DexType> stateKeyGetter,
+ PrintStream out) {
+ DexType stateKey = stateKeyGetter.apply(parent);
+ out.print(indentation);
+ out.print(stateKey != null ? stateKey.toSourceString() : "<?>");
+ out.print(".");
+ out.print(name.toSourceString());
+ out.println(proto.toSmaliString());
+ parent.printState(proto, indentation + " ", out);
+ }
}
// Shuffles the given methods if assertions are enabled and deterministic debugging is disabled.
diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/NamingState.java
index f0f1720..d414b3b 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingState.java
@@ -11,10 +11,12 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
+import java.io.PrintStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
@@ -129,6 +131,18 @@
state.addRenaming(original, key, newName);
}
+ void printState(ProtoType proto, String indentation, PrintStream out) {
+ KeyType key = keyTransform.apply(proto);
+ InternalState state = findInternalStateFor(key);
+ if (state != null) {
+ state.printReservedNames(indentation, out);
+ state.printRenamings(indentation, out);
+ } else {
+ out.print(indentation);
+ out.println("<NO STATE>");
+ }
+ }
+
class InternalState {
private static final int INITIAL_NAME_COUNT = 1;
@@ -229,5 +243,49 @@
return StringUtils.numberToIdentifier(EMPTY_CHAR_ARRAY, nameCount++, false);
}
}
+
+ void printReservedNames(String indentation, PrintStream out) {
+ out.print(indentation);
+ out.print("Reserved names:");
+ if (reservedNames == null || reservedNames.isEmpty()) {
+ out.print(" <NO RESERVED NAMES>");
+ } else {
+ for (DexString reservedName : reservedNames) {
+ out.print(System.lineSeparator());
+ out.print(indentation);
+ out.print(" ");
+ out.print(reservedName.toSourceString());
+ }
+ }
+ out.println();
+ if (parentInternalState != null) {
+ parentInternalState.printReservedNames(indentation + " ", out);
+ }
+ }
+
+ void printRenamings(String indentation, PrintStream out) {
+ out.print(indentation);
+ out.print("Renamings:");
+ if (renamings == null || renamings.isEmpty()) {
+ out.print(" <NO RENAMINGS>");
+ } else {
+ for (DexString original : renamings.rowKeySet()) {
+ Map<KeyType, DexString> row = renamings.row(original);
+ for (Entry<KeyType, DexString> entry : row.entrySet()) {
+ out.print(System.lineSeparator());
+ out.print(indentation);
+ out.print(" ");
+ out.print(original.toSourceString());
+ out.print(entry.getKey().toString());
+ out.print(" -> ");
+ out.print(entry.getValue().toSourceString());
+ }
+ }
+ }
+ out.println();
+ if (parentInternalState != null) {
+ parentInternalState.printRenamings(indentation + " ", out);
+ }
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index 0e3d988..decd262 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.MapMaker;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
@@ -150,6 +151,17 @@
returnType == Void.TYPE ? null : typeFromClass(returnType));
}
+ /** Get a method reference from a Java reflection constructor. */
+ public static MethodReference methodFromMethod(Constructor<?> method) {
+ Class<?> holderClass = method.getDeclaringClass();
+ Class<?>[] parameterTypes = method.getParameterTypes();
+ ImmutableList.Builder<TypeReference> builder = ImmutableList.builder();
+ for (Class<?> parameterType : parameterTypes) {
+ builder.add(typeFromClass(parameterType));
+ }
+ return method(classFromClass(holderClass), "<init>", builder.build(), null);
+ }
+
/** Get a field reference from its full reference specification. */
public static FieldReference field(
ClassReference holderClass, String fieldName, TypeReference fieldType) {
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 f011931..6d24a8c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -307,6 +307,10 @@
this.options = options;
}
+ private static <T> SetWithReason<T> newSetWithoutReasonReporter() {
+ return new SetWithReason<>((f, r) -> {});
+ }
+
private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRule>> items) {
items.entrySet().forEach(this::enqueueRootItem);
}
@@ -1097,6 +1101,7 @@
SetWithReason<DexEncodedField> reachableFields = reachableInstanceFields.get(type);
if (reachableFields != null) {
for (DexEncodedField field : reachableFields.getItems()) {
+ // TODO(b/120959039): Should the reason this field is reachable come from the set?
markInstanceFieldAsLive(field, KeepReason.reachableFromLiveType(type));
}
}
@@ -1241,14 +1246,14 @@
if (encodedField.accessFlags.isStatic()) {
markStaticFieldAsLive(encodedField.field, reason);
} else {
- SetWithReason<DexEncodedField> reachable =
- reachableInstanceFields.computeIfAbsent(
- encodedField.field.clazz, ignore -> new SetWithReason<>((f, r) -> {}));
- // TODO(b/120959039): The reachable.add test might be hiding other paths to the field.
- if (reachable.add(encodedField, reason)
- && isInstantiatedOrHasInstantiatedSubtype(encodedField.field.clazz)) {
+ if (isInstantiatedOrHasInstantiatedSubtype(encodedField.field.clazz)) {
// We have at least one live subtype, so mark it as live.
markInstanceFieldAsLive(encodedField, reason);
+ } else {
+ // Add the field to the reachable set if the type later becomes instantiated.
+ reachableInstanceFields
+ .computeIfAbsent(encodedField.field.clazz, ignore -> newSetWithoutReasonReporter())
+ .add(encodedField, reason);
}
}
}
@@ -1292,7 +1297,7 @@
// TODO(b/120959039): The reachable.add test might be hiding other paths to the method.
SetWithReason<DexEncodedMethod> reachable =
reachableVirtualMethods.computeIfAbsent(
- encodedMethod.method.holder, (ignore) -> new SetWithReason<>((m, r) -> {}));
+ encodedMethod.method.holder, ignore -> newSetWithoutReasonReporter());
if (reachable.add(encodedMethod, reason)) {
// Abstract methods cannot be live.
if (!encodedMethod.accessFlags.isAbstract()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index e3a8273..5acd2a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1433,11 +1433,27 @@
}
private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
+ IdentifierPatternWithWildcardsAndNegation pattern =
+ acceptIdentifierWithBackreference(kind, false);
+ if (pattern == null) {
+ return null;
+ }
+ assert !pattern.negated;
+ return pattern.patternWithWildcards;
+ }
+
+ private IdentifierPatternWithWildcardsAndNegation acceptIdentifierWithBackreference(
+ IdentifierType kind, boolean allowNegation) {
ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
StringBuilder currentAsterisks = null;
int asteriskCount = 0;
StringBuilder currentBackreference = null;
skipWhitespace();
+
+ final char quote = acceptQuoteIfPresent();
+ final boolean quoted = isQuote(quote);
+ final boolean negated = allowNegation ? acceptChar('!') : false;
+
int start = position;
int end = position;
while (!eof(end)) {
@@ -1516,9 +1532,17 @@
currentBackreference = new StringBuilder();
end += Character.charCount(current);
} else {
+ if (quoted && quote != current) {
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Invalid character '" + (char) current + "', expected end-quote.",
+ origin,
+ getPosition()));
+ }
break;
}
}
+ position = quoted ? end + 1 : end;
if (currentAsterisks != null) {
wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
}
@@ -1530,10 +1554,8 @@
if (start == end) {
return null;
}
- position = end;
- return new IdentifierPatternWithWildcards(
- contents.substring(start, end),
- wildcardsCollector.build());
+ return new IdentifierPatternWithWildcardsAndNegation(
+ contents.substring(start, end), wildcardsCollector.build(), negated);
}
private String acceptFieldNameOrIntegerForReturn() {
@@ -1627,20 +1649,20 @@
}
}
+ private void parseClassNameAddToBuilder(ProguardClassNameList.Builder builder)
+ throws ProguardRuleParserException {
+ IdentifierPatternWithWildcardsAndNegation name = parseClassName(true);
+ builder.addClassName(
+ name.negated,
+ ProguardTypeMatcher.create(name.patternWithWildcards, ClassOrType.CLASS, dexItemFactory));
+ skipWhitespace();
+ }
+
private ProguardClassNameList parseClassNames() throws ProguardRuleParserException {
ProguardClassNameList.Builder builder = ProguardClassNameList.builder();
- skipWhitespace();
- boolean negated = acceptChar('!');
- builder.addClassName(negated,
- ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
- skipWhitespace();
- while (acceptChar(',')) {
- skipWhitespace();
- negated = acceptChar('!');
- builder.addClassName(negated,
- ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
- skipWhitespace();
- }
+ do {
+ parseClassNameAddToBuilder(builder);
+ } while (acceptChar(','));
return builder.build();
}
@@ -1650,8 +1672,15 @@
}
private IdentifierPatternWithWildcards parseClassName() throws ProguardRuleParserException {
- IdentifierPatternWithWildcards name =
- acceptIdentifierWithBackreference(IdentifierType.CLASS_NAME);
+ IdentifierPatternWithWildcardsAndNegation name = parseClassName(false);
+ assert !name.negated;
+ return name.patternWithWildcards;
+ }
+
+ private IdentifierPatternWithWildcardsAndNegation parseClassName(boolean allowNegation)
+ throws ProguardRuleParserException {
+ IdentifierPatternWithWildcardsAndNegation name =
+ acceptIdentifierWithBackreference(IdentifierType.CLASS_NAME, allowNegation);
if (name == null) {
throw parseError("Class name expected");
}
@@ -1838,4 +1867,15 @@
return false;
}
}
+
+ static class IdentifierPatternWithWildcardsAndNegation {
+ final IdentifierPatternWithWildcards patternWithWildcards;
+ final boolean negated;
+
+ IdentifierPatternWithWildcardsAndNegation(
+ String pattern, List<ProguardWildcard> wildcards, boolean negated) {
+ patternWithWildcards = new IdentifierPatternWithWildcards(pattern, wildcards);
+ this.negated = negated;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index a5854b6..85c4876 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -21,15 +21,18 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.optimize.Inliner;
+import com.android.tools.r8.naming.InterfaceMethodNameMinifier;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.utils.IROrdering.IdentityIROrdering;
import com.android.tools.r8.utils.IROrdering.NondeterministicIROrdering;
+import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -223,6 +226,9 @@
}
public Set<String> extensiveLoggingFilter = getExtensiveLoggingFilter();
+ public Set<String> extensiveInterfaceMethodMinifierLoggingFilter =
+ getExtensiveInterfaceMethodMinifierLoggingFilter();
+
public List<String> methodsFilter = ImmutableList.of();
public int minApiLevel = AndroidApiLevel.getDefault().getLevel();
// Skipping min_api check and compiling an intermediate result intended for later merging.
@@ -298,6 +304,19 @@
return ImmutableSet.of();
}
+ private static Set<String> getExtensiveInterfaceMethodMinifierLoggingFilter() {
+ String property =
+ System.getProperty("com.android.tools.r8.extensiveInterfaceMethodMinifierLoggingFilter");
+ if (property != null) {
+ ImmutableSet.Builder<String> builder = ImmutableSet.builder();
+ for (String method : property.split(";")) {
+ builder.add(method);
+ }
+ return builder.build();
+ }
+ return ImmutableSet.of();
+ }
+
public static class InvalidParameterAnnotationInfo {
final DexMethod method;
@@ -543,6 +562,21 @@
public boolean forceNameReflectionOptimization = false;
public boolean disallowLoadStoreOptimization = false;
public Consumer<IRCode> irModifier = null;
+
+ public MinifierTestingOptions minifier = new MinifierTestingOptions();
+
+ public static class MinifierTestingOptions {
+
+ public Comparator<DexMethod> interfaceMethodOrdering = null;
+
+ public Comparator<Wrapper<DexMethod>> createInterfaceMethodOrdering(
+ InterfaceMethodNameMinifier minifier) {
+ if (interfaceMethodOrdering != null) {
+ return (a, b) -> interfaceMethodOrdering.compare(a.get(), b.get());
+ }
+ return minifier.createDefaultInterfaceMethodOrdering();
+ }
+ }
}
private boolean hasMinApi(AndroidApiLevel level) {
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 6b51909..c191d9a 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -507,6 +507,12 @@
assertEquals(0, new ZipFile(emptyZip.toFile(), StandardCharsets.UTF_8).size());
}
+ @Test(expected = CompilationFailedException.class)
+ public void missingParameterForLastOption() throws CompilationFailedException {
+ DiagnosticsChecker.checkErrorsContains(
+ "Missing parameter", handler -> parse(handler, "--output"));
+ }
+
private D8Command parse(String... args) throws CompilationFailedException {
return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
}
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 5331ca9..980e495 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -619,6 +619,12 @@
runCustomResourceProcessing(false, false, 0);
}
+ @Test(expected = CompilationFailedException.class)
+ public void missingParameterForLastOption() throws CompilationFailedException {
+ DiagnosticsChecker.checkErrorsContains(
+ "Missing parameter", handler -> parse(handler, "--output"));
+ }
+
private R8Command parse(String... args) throws CompilationFailedException {
return R8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
}
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 55730eb..ecf247c 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -57,7 +57,6 @@
return self();
}
-
public String proguardMap() {
return proguardMap;
}
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java
new file mode 100644
index 0000000..f2e1a60
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceMethodNameMinifierTest.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.FileUtils;
+import java.nio.file.Path;
+import org.junit.Test;
+
+/** Regression test for b/123730537. */
+public class InterfaceMethodNameMinifierTest extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+ FileUtils.writeTextFile(dictionary, "a", "b", "c");
+
+ testForR8(Backend.DEX)
+ .addInnerClasses(InterfaceMethodNameMinifierTest.class)
+ .addKeepRules(
+ "-keep,allowobfuscation interface * { <methods>; }",
+ "-keep,allowobfuscation class * { <methods>; }",
+ "-keep interface " + L.class.getTypeName() + " { <methods>; }",
+ "-obfuscationdictionary " + dictionary.toString())
+ // Minify the interface methods in alphabetic order.
+ .addOptionsModification(
+ options -> options.testing.minifier.interfaceMethodOrdering = DexMethod::slowCompareTo)
+ .compile();
+ }
+
+ interface I {}
+
+ interface J extends I {
+
+ // Will be renamed first, to a().
+ void a();
+
+ // Will be renamed secondly. Should be renamed to c(), and not b(), because `void K.b()` will
+ // be renamed to b() because `void L.b()` is reserved.
+ void c();
+ }
+
+ interface K extends I {
+
+ // Will be renamed thirdly, to b(), because `void L.b()` is reserved.
+ void b();
+ }
+
+ interface L {
+
+ // Reserved. Will be renamed together with `void K.b()`.
+ void b();
+ }
+
+ static class Implementation implements J, K {
+
+ @Override
+ public void a() {}
+
+ @Override
+ public void b() {}
+
+ @Override
+ public void c() {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index f3aba75..cac6dd3 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -2402,12 +2402,14 @@
@Test
public void b124181032() throws Exception {
+ // Test spaces and quotes in class name list.
ProguardConfigurationParser parser;
parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(
createConfigurationForTesting(
ImmutableList.of(
- "-keepclassmembers class a.b.c.**, !**Client, !**Interceptor {",
+ "-keepclassmembers class \"a.b.c.**\" ,"
+ + " !**d , '!**e' , \"!**f\" , g , 'h' , \"i\" { ",
"<fields>;",
"<init>();",
"}")));
@@ -2415,6 +2417,6 @@
assertEquals(1, rules.size());
ProguardConfigurationRule rule = rules.get(0);
assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS.toString(), rule.typeString());
- assertEquals("a.b.c.**,!**Client,!**Interceptor", rule.getClassNames().toString());
+ assertEquals("a.b.c.**,!**d,!**e,!**f,g,h,i", rule.getClassNames().toString());
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
index e518181..3ba9b99 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -8,9 +8,7 @@
@Keep
public class KeptByFieldReflectionTest {
- // TODO(b/123262024): This field must be kept un-initialized. Otherwise the "-whyareyoukeeping"
- // output tested will hit the initialization in <init> and not the reflective access.
- public int foo;
+ public int foo = 42;
public static void main(String[] args) throws Exception {
// Due to b/123210548 the object cannot be created by a reflective newInstance call.
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
index 62dcebe..6ba1f08 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -30,7 +30,7 @@
private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
- private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 0");
+ private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 42");
private final String EXPECTED_WHYAREYOUKEEPING =
StringUtils.lines(
@@ -55,6 +55,7 @@
public void test() throws Exception {
MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
+ MethodReference fooInit = methodFromMethod(CLASS.getDeclaredConstructor());
if (backend == Backend.CF) {
testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
@@ -80,6 +81,11 @@
inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+ // The field is primarily kept by the reflective lookup in main.
inspector.field(fooField).assertRenamed().assertReflectedFrom(mainMethod);
+
+ // The field is also kept by the write in Foo.<init>.
+ // We may want to change that behavior. See b/124428834.
+ inspector.field(fooField).assertRenamed().assertKeptBy(inspector.method(fooInit));
}
}
diff --git a/third_party/opensource_apps.tar.gz.sha1 b/third_party/opensource_apps.tar.gz.sha1
index 9fb2bb2..4ee5113 100644
--- a/third_party/opensource_apps.tar.gz.sha1
+++ b/third_party/opensource_apps.tar.gz.sha1
@@ -1 +1 @@
-64b0689bce8f6789320b34da4e91c6dbcc1296e9
\ No newline at end of file
+efed1b4bf62340b626cf2d0addb4f334664c0727
\ No newline at end of file