Merge commit '38b1992e641eac6387983444f5cac0f2ba61379a' into dev-release
diff --git a/infra/config/global/cr-buildbucket.cfg b/infra/config/global/cr-buildbucket.cfg
index a804d02..b42f30e 100644
--- a/infra/config/global/cr-buildbucket.cfg
+++ b/infra/config/global/cr-buildbucket.cfg
@@ -305,12 +305,23 @@
mixins: "normal"
recipe {
properties: "run_on_apps:True"
+ properties: "recompilation:False"
+ }
+ }
+ builders {
+ name: "linux-run-on-as-app-recompilation"
+ mixins: "linux"
+ mixins: "normal"
+ recipe {
+ properties: "run_on_apps:True"
+ properties: "recompilation:True"
}
}
builders {
name: "linux-run-on-as-app_release"
mixins: "linux"
mixins: "normal"
+ execution_timeout_secs: 25200 # 7h
recipe {
properties: "run_on_apps:True"
}
diff --git a/infra/config/global/luci-milo.cfg b/infra/config/global/luci-milo.cfg
index 30b9ede..7bdd5c2 100644
--- a/infra/config/global/luci-milo.cfg
+++ b/infra/config/global/luci-milo.cfg
@@ -61,6 +61,11 @@
short_name: "apps"
}
builders {
+ name: "buildbucket/luci.r8.ci/linux-run-on-as-app-recompilation"
+ category: "R8"
+ short_name: "apps-rec"
+ }
+ builders {
name: "buildbucket/luci.r8.ci/linux-jctf"
category: "R8"
short_name: "jctf"
diff --git a/infra/config/global/luci-notify.cfg b/infra/config/global/luci-notify.cfg
index 16015f2..bcd03ba 100644
--- a/infra/config/global/luci-notify.cfg
+++ b/infra/config/global/luci-notify.cfg
@@ -119,6 +119,11 @@
repository: "https://r8.googlesource.com/r8"
}
builders {
+ name: "linux-run-on-as-app-recompilation"
+ bucket: "ci"
+ repository: "https://r8.googlesource.com/r8"
+ }
+ builders {
name: "linux-run-on-as-app_release"
bucket: "ci"
repository: "https://r8.googlesource.com/r8"
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index c55893a..5383ff2 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -35,6 +35,7 @@
triggers: "linux-android-8.1.0"
triggers: "linux-android-9.0.0"
triggers: "linux-run-on-as-app"
+ triggers: "linux-run-on-as-app-recompilation"
triggers: "linux-internal"
triggers: "linux-jctf"
triggers: "r8cf-linux-jctf"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 56f69f7..c75c2fd 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -56,6 +56,7 @@
import com.android.tools.r8.shaking.AbstractMethodRemover;
import com.android.tools.r8.shaking.AnnotationRemover;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
import com.android.tools.r8.shaking.DiscardedChecker;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerFactory;
@@ -68,6 +69,7 @@
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.StaticClassMerger;
import com.android.tools.r8.shaking.TreePruner;
+import com.android.tools.r8.shaking.TreePrunerConfiguration;
import com.android.tools.r8.shaking.VerticalClassMerger;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -346,10 +348,11 @@
if (options.isShrinking()) {
// Mark dead proto extensions fields as neither being read nor written. This step must
// run prior to the tree pruner.
- appView.withGeneratedExtensionRegistryShrinker(GeneratedExtensionRegistryShrinker::run);
+ appView.withGeneratedExtensionRegistryShrinker(
+ shrinker -> shrinker.run(enqueuer.getMode()));
- TreePruner pruner = new TreePruner(application, appView.withLiveness());
- application = pruner.run();
+ TreePruner pruner = new TreePruner(appViewWithLiveness);
+ application = pruner.run(application);
// Recompute the subtyping information.
appView.setAppInfo(
@@ -630,10 +633,13 @@
if (options.isShrinking()) {
// Mark dead proto extensions fields as neither being read nor written. This step must
// run prior to the tree pruner.
- appView.withGeneratedExtensionRegistryShrinker(GeneratedExtensionRegistryShrinker::run);
+ TreePrunerConfiguration treePrunerConfiguration =
+ appView.withGeneratedExtensionRegistryShrinker(
+ shrinker -> shrinker.run(enqueuer.getMode()),
+ DefaultTreePrunerConfiguration.getInstance());
- TreePruner pruner = new TreePruner(application, appViewWithLiveness);
- application = pruner.run();
+ TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
+ application = pruner.run(application);
if (options.usageInformationConsumer != null) {
ExceptionUtils.withFinishedResourceHandler(
options.reporter, options.usageInformationConsumer);
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 4549d8e..122e1c8 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.ProgramResourceProvider;
@@ -42,6 +43,8 @@
import com.android.tools.r8.utils.Timing;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
@@ -59,7 +62,7 @@
private final InternalOptions options;
private final DexItemFactory itemFactory;
private final Timing timing;
- private AndroidApp inputApp;
+ private final AndroidApp inputApp;
public interface ProgramClassConflictResolver {
DexProgramClass resolveClassConflict(DexProgramClass a, DexProgramClass b);
@@ -107,9 +110,30 @@
ProgramClassConflictResolver resolver)
throws IOException, ExecutionException {
assert verifyMainDexOptionsCompatible(inputApp, options);
+ Path dumpOutput = null;
+ boolean cleanDump = false;
if (options.dumpInputToFile != null) {
- inputApp = dumpInputToFile(inputApp, options);
- throw options.reporter.fatalError("Dumped compilation inputs to: " + options.dumpInputToFile);
+ dumpOutput = Paths.get(options.dumpInputToFile);
+ } else if (options.dumpInputToDirectory != null) {
+ dumpOutput =
+ Paths.get(options.dumpInputToDirectory).resolve("dump" + System.nanoTime() + ".zip");
+ } else if (options.testing.dumpAll) {
+ cleanDump = true;
+ dumpOutput = Paths.get("/tmp").resolve("dump" + System.nanoTime() + ".zip");
+ }
+ if (dumpOutput != null) {
+ timing.begin("ApplicationReader.dump");
+ dumpInputToFile(inputApp, dumpOutput, options);
+ if (cleanDump) {
+ Files.delete(dumpOutput);
+ }
+ timing.end();
+ Diagnostic message = new StringDiagnostic("Dumped compilation inputs to: " + dumpOutput);
+ if (options.dumpInputToFile != null) {
+ throw options.reporter.fatalError(message);
+ } else if (!cleanDump) {
+ options.reporter.info(message);
+ }
}
timing.begin("DexApplication.read");
final LazyLoadedDexApplication.Builder builder =
@@ -145,9 +169,8 @@
return builder.build();
}
- private static AndroidApp dumpInputToFile(AndroidApp app, InternalOptions options) {
- return app.dump(
- Paths.get(options.dumpInputToFile), options.getProguardConfiguration(), options.reporter);
+ private static void dumpInputToFile(AndroidApp app, Path output, InternalOptions options) {
+ app.dump(output, options.getProguardConfiguration(), options.reporter);
}
private static boolean verifyMainDexOptionsCompatible(
diff --git a/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java b/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
index 540502b..ffdb9a4 100644
--- a/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
+++ b/src/main/java/com/android/tools/r8/dex/ClassesChecksum.java
@@ -18,7 +18,7 @@
private static final char PREFIX_CHAR1 = '~';
private static final char PREFIX_CHAR2 = '~';
- private Object2LongMap<String> dictionary = null;
+ private final Object2LongMap<String> dictionary = new Object2LongOpenHashMap<>();
public ClassesChecksum() {
assert PREFIX.length() == 3;
@@ -27,14 +27,7 @@
assert PREFIX.charAt(2) == PREFIX_CHAR2;
}
- private void ensureMap() {
- if (dictionary == null) {
- dictionary = new Object2LongOpenHashMap<>();
- }
- }
-
private void append(JsonObject json) {
- ensureMap();
json.entrySet()
.forEach(
entry ->
@@ -42,7 +35,6 @@
}
public void addChecksum(String classDescriptor, long crc) {
- ensureMap();
dictionary.put(classDescriptor, crc);
}
@@ -86,14 +78,15 @@
* @param string String to check if definitely preceded the checksum marker.
* @return If the string passed definitely preceded the checksum marker
*/
- public static boolean definitelyPreceedChecksumMarker(DexString string) {
+ public static boolean definitelyPrecedesChecksumMarker(DexString string) {
try {
assert PREFIX.length() == 3;
- char[] prefix = string.decodePrefix(3);
- return prefix.length == 0
- || (prefix.length == 1 && prefix[0] <= PREFIX_CHAR0)
- || (prefix.length == 2 && prefix[0] == PREFIX_CHAR0 && prefix[1] <= PREFIX_CHAR1)
- || (prefix.length == 3
+ char[] prefix = new char[PREFIX.length()];
+ int prefixLength = string.decodePrefix(prefix);
+ return prefixLength == 0
+ || (prefixLength == 1 && prefix[0] <= PREFIX_CHAR0)
+ || (prefixLength == 2 && prefix[0] == PREFIX_CHAR0 && prefix[1] <= PREFIX_CHAR1)
+ || (prefixLength == 3
&& prefix[0] == PREFIX_CHAR0
&& prefix[1] == PREFIX_CHAR1
&& prefix[2] < PREFIX_CHAR2);
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 5989981..20678fe 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -64,8 +64,8 @@
import com.android.tools.r8.utils.Pair;
import com.google.common.io.ByteStreams;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -117,10 +117,10 @@
private OffsetToObjectMapping indexedItems = new OffsetToObjectMapping();
// Mapping from offset to code item;
- private Int2ObjectMap<DexCode> codes = new Int2ObjectOpenHashMap<>();
+ private Int2ReferenceMap<DexCode> codes = new Int2ReferenceOpenHashMap<>();
// Mapping from offset to dex item;
- private Int2ObjectMap<Object> offsetMap = new Int2ObjectOpenHashMap<>();
+ private Int2ReferenceMap<Object> offsetMap = new Int2ReferenceOpenHashMap<>();
// Factory to canonicalize certain dexitems.
private final DexItemFactory dexItemFactory;
@@ -143,7 +143,7 @@
}
if (codes == null) {
- codes = new Int2ObjectOpenHashMap<>();
+ codes = new Int2ReferenceOpenHashMap<>();
}
if (classKind == ClassKind.LIBRARY) {
@@ -950,7 +950,7 @@
ClassesChecksum parsedChecksums = new ClassesChecksum();
for (int i = stringIDs.length - 1; i >= 0; i--) {
DexString value = indexedItems.getString(i);
- if (ClassesChecksum.definitelyPreceedChecksumMarker(value)) {
+ if (ClassesChecksum.definitelyPrecedesChecksumMarker(value)) {
break;
}
parsedChecksums.tryParseAndAppend(value);
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index d569852..139a285 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -641,11 +641,6 @@
return null;
}
- public void registerNewType(DexType newType, DexType superType) {
- // We do not track subtyping relationships in the basic AppInfo. So do nothing.
- assert checkIfObsolete();
- }
-
public boolean isInMainDexList(DexType type) {
assert checkIfObsolete();
return app.mainDexList.contains(type);
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index faa25c2..fa4cea3 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -3,11 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
+
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.SetUtils;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -48,7 +51,7 @@
// Caching what interfaces this type is implementing. This includes super-interface hierarchy.
Set<DexType> implementedInterfaces = null;
- public TypeInfo(DexType type) {
+ TypeInfo(DexType type) {
this.type = type;
}
@@ -78,7 +81,7 @@
}
}
- public synchronized void addDirectSubtype(TypeInfo subtypeInfo) {
+ synchronized void addDirectSubtype(TypeInfo subtypeInfo) {
assert hierarchyLevel != UNKNOWN_LEVEL;
ensureDirectSubTypeSet();
directSubtypes.add(subtypeInfo.type);
@@ -149,6 +152,28 @@
@Override
public void addSynthesizedClass(DexProgramClass synthesizedClass) {
super.addSynthesizedClass(synthesizedClass);
+ // Register synthesized type, which has two side effects:
+ // 1) Set the hierarchy level of synthesized type based on that of its super type,
+ // 2) Register the synthesized type as a subtype of the supertype.
+ //
+ // The first one makes method resolutions on that synthesized class free from assertion errors
+ // about unknown hierarchy level.
+ //
+ // For the second one, note that such addition is synchronized, but the retrieval of direct
+ // subtypes isn't. Thus, there is a chance of race conditions: utils that check/iterate direct
+ // subtypes, e.g., allImmediateSubtypes, hasSubTypes, etc., may not be able to see this new
+ // synthesized class. However, in practice, this would be okay because, in most cases,
+ // synthesized class's super type is Object, which in general has other subtypes in any way.
+ // Also, iterating all subtypes of Object usually happens before/after IR processing, i.e., as
+ // part of structural changes, such as bottom-up traversal to collect all method signatures,
+ // which are free from such race conditions. Another exceptional case is synthesized classes
+ // whose synthesis is isolated from IR processing. For example, lambda group class that merges
+ // lambdas with the same interface are synthesized/finalized even after post processing of IRs.
+ assert synthesizedClass.superType == dexItemFactory().objectType
+ || synthesizedClass.type.toString().contains(LAMBDA_GROUP_CLASS_NAME_PREFIX)
+ : "Make sure retrieval and iteration of sub types of `" + synthesizedClass.superType
+ + "` is guaranteed to be thread safe and able to see `" + synthesizedClass + "`";
+ registerNewType(synthesizedClass.type, synthesizedClass.superType);
// TODO(b/129458850): Remove when we no longer synthesize classes on-the-fly.
Set<DexType> visited = SetUtils.newIdentityHashSet(synthesizedClass.allImmediateSupertypes());
@@ -217,8 +242,11 @@
return typeInfo.computeIfAbsent(type, TypeInfo::new);
}
- private void populateAllSuperTypes(Map<DexType, Set<DexType>> map, DexType holder,
- DexClass baseClass, Function<DexType, DexClass> definitions) {
+ private void populateAllSuperTypes(
+ Map<DexType, Set<DexType>> map,
+ DexType holder,
+ DexClass baseClass,
+ Function<DexType, DexClass> definitions) {
DexClass holderClass = definitions.apply(holder);
// Skip if no corresponding class is found.
if (holderClass != null) {
@@ -435,13 +463,17 @@
|| bootstrapMethod.asMethod() == dexItemFactory().stringConcatMethod);
}
- @Override
- public void registerNewType(DexType newType, DexType superType) {
+ private void registerNewType(DexType newType, DexType superType) {
assert checkIfObsolete();
// Register the relationship between this type and its superType.
getTypeInfo(superType).addDirectSubtype(getTypeInfo(newType));
}
+ @VisibleForTesting
+ public void registerNewTypeForTesting(DexType newType, DexType superType) {
+ registerNewType(newType, superType);
+ }
+
@Override
public boolean hasSubtyping() {
assert checkIfObsolete();
@@ -495,7 +527,7 @@
// Depending on optimizations, conservative answer of subtype relation may vary.
// Pass different `orElse` in that case.
- public boolean isStrictSubtypeOf(DexType subtype, DexType supertype, boolean orElse) {
+ private boolean isStrictSubtypeOf(DexType subtype, DexType supertype, boolean orElse) {
if (subtype == supertype) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 776cbaf..b67931f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -46,10 +46,8 @@
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.NestUtils;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.DefaultCallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.MutableCallSiteOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -134,8 +132,7 @@
// we need to maintain a set of states with (potentially different) contexts.
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private MethodOptimizationInfo optimizationInfo = DefaultMethodOptimizationInfo.DEFAULT_INSTANCE;
- private CallSiteOptimizationInfo callSiteOptimizationInfo =
- DefaultCallSiteOptimizationInfo.getInstance();
+ private CallSiteOptimizationInfo callSiteOptimizationInfo = CallSiteOptimizationInfo.BOTTOM;
private int classFileVersion = -1;
private DexEncodedMethod defaultInterfaceMethodImplementation = null;
@@ -1167,22 +1164,15 @@
optimizationInfo = info;
}
- public CallSiteOptimizationInfo getCallSiteOptimizationInfo() {
+ public synchronized CallSiteOptimizationInfo getCallSiteOptimizationInfo() {
checkIfObsolete();
return callSiteOptimizationInfo;
}
- public synchronized MutableCallSiteOptimizationInfo getMutableCallSiteOptimizationInfo(
- AppView<?> appView) {
+ public synchronized void joinCallSiteOptimizationInfo(
+ CallSiteOptimizationInfo other, AppView<?> appView) {
checkIfObsolete();
- if (callSiteOptimizationInfo.isDefaultCallSiteOptimizationInfo()) {
- MutableCallSiteOptimizationInfo mutableOptimizationInfo =
- new MutableCallSiteOptimizationInfo(this);
- callSiteOptimizationInfo = mutableOptimizationInfo;
- return mutableOptimizationInfo;
- }
- assert callSiteOptimizationInfo.isMutableCallSiteOptimizationInfo();
- return callSiteOptimizationInfo.asMutableCallSiteOptimizationInfo();
+ callSiteOptimizationInfo = callSiteOptimizationInfo.join(other, appView, this);
}
public void copyMetadata(DexEncodedMethod from) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index dbeb35f..c7e1199 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -37,9 +37,9 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
-import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -69,11 +69,11 @@
new ConcurrentHashMap<>();
// DexDebugEvent Canonicalization.
- private final Int2ObjectMap<AdvanceLine> advanceLines = new Int2ObjectOpenHashMap<>();
- private final Int2ObjectMap<AdvancePC> advancePCs = new Int2ObjectOpenHashMap<>();
- private final Int2ObjectMap<Default> defaults = new Int2ObjectOpenHashMap<>();
- private final Int2ObjectMap<EndLocal> endLocals = new Int2ObjectOpenHashMap<>();
- private final Int2ObjectMap<RestartLocal> restartLocals = new Int2ObjectOpenHashMap<>();
+ private final Int2ReferenceMap<AdvanceLine> advanceLines = new Int2ReferenceOpenHashMap<>();
+ private final Int2ReferenceMap<AdvancePC> advancePCs = new Int2ReferenceOpenHashMap<>();
+ private final Int2ReferenceMap<Default> defaults = new Int2ReferenceOpenHashMap<>();
+ private final Int2ReferenceMap<EndLocal> endLocals = new Int2ReferenceOpenHashMap<>();
+ private final Int2ReferenceMap<RestartLocal> restartLocals = new Int2ReferenceOpenHashMap<>();
private final SetEpilogueBegin setEpilogueBegin = new SetEpilogueBegin();
private final SetPrologueEnd setPrologueEnd = new SetPrologueEnd();
private final Map<DexString, SetFile> setFiles = new HashMap<>();
@@ -1312,7 +1312,7 @@
private static DexType[] applyClassMappingToDexTypes(
DexType[] types, Function<DexType, DexType> mapping) {
- Map<Integer, DexType> changed = new Int2ObjectArrayMap<>();
+ Map<Integer, DexType> changed = new Int2ReferenceArrayMap<>();
for (int i = 0; i < types.length; i++) {
DexType applied = mapping.apply(types[i]);
if (applied != types[i]) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index a6591b4..131cee6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -111,66 +111,35 @@
return result;
}
- // Inspired from /dex/src/main/java/com/android/dex/Mutf8.java
private String decode() throws UTFDataFormatException {
- int s = 0;
- int p = 0;
char[] out = new char[size];
- while (true) {
- char a = (char) (content[p++] & 0xff);
- if (a == 0) {
- return new String(out, 0, s);
- }
- out[s] = a;
- if (a < '\u0080') {
- s++;
- } else if ((a & 0xe0) == 0xc0) {
- int b = content[p++] & 0xff;
- if ((b & 0xC0) != 0x80) {
- throw new UTFDataFormatException("bad second byte");
- }
- out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
- } else if ((a & 0xf0) == 0xe0) {
- int b = content[p++] & 0xff;
- int c = content[p++] & 0xff;
- if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
- throw new UTFDataFormatException("bad second or third byte");
- }
- out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
- } else {
- throw new UTFDataFormatException("bad byte");
- }
- }
+ int decodedLength = decodePrefix(out);
+ return new String(out, 0, decodedLength);
}
- public char[] decodePrefix(int prefixLength) throws UTFDataFormatException {
+ // Inspired from /dex/src/main/java/com/android/dex/Mutf8.java
+ public int decodePrefix(char[] out) throws UTFDataFormatException {
int s = 0;
int p = 0;
- char[] out = new char[prefixLength];
+ int prefixLength = out.length;
while (true) {
char a = (char) (content[p++] & 0xff);
if (a == 0) {
- if (s == prefixLength) {
- return out;
- }
- char[] result = new char[s];
- System.arraycopy(out, 0, result, 0, s);
- return result;
+ return s;
}
out[s] = a;
if (a < '\u0080') {
- s++;
- if (s == prefixLength) {
- return out;
+ if (++s == prefixLength) {
+ return s;
}
} else if ((a & 0xe0) == 0xc0) {
int b = content[p++] & 0xff;
if ((b & 0xC0) != 0x80) {
throw new UTFDataFormatException("bad second byte");
}
- out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
- if (s == prefixLength) {
- return out;
+ out[s] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
+ if (++s == prefixLength) {
+ return s;
}
} else if ((a & 0xf0) == 0xe0) {
int b = content[p++] & 0xff;
@@ -178,9 +147,9 @@
if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
throw new UTFDataFormatException("bad second or third byte");
}
- out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
- if (s == prefixLength) {
- return out;
+ out[s] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
+ if (++s == prefixLength) {
+ return s;
}
} else {
throw new UTFDataFormatException("bad byte");
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index af70929..cbce2db 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -25,6 +25,7 @@
import org.objectweb.asm.Type;
public abstract class DexValue extends DexItem {
+
public static final DexValue[] EMPTY_ARRAY = {};
public static final UnknownDexValue UNKNOWN = UnknownDexValue.UNKNOWN;
@@ -72,6 +73,14 @@
return false;
}
+ public boolean isDexValueInt() {
+ return false;
+ }
+
+ public DexValueInt asDexValueInt() {
+ return null;
+ }
+
public static DexValue fromAsmBootstrapArgument(
Object value, JarApplicationReader application, DexType clazz) {
if (value instanceof Integer) {
@@ -473,6 +482,16 @@
}
@Override
+ public boolean isDexValueInt() {
+ return true;
+ }
+
+ @Override
+ public DexValueInt asDexValueInt() {
+ return this;
+ }
+
+ @Override
public Object asAsmEncodedObject() {
return Integer.valueOf(value);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 624cf90..a5fbaaf 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -14,19 +14,29 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRCodeUtils;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.TreePrunerConfiguration;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -66,6 +76,7 @@
private final AppView<AppInfoWithLiveness> appView;
private final ProtoReferences references;
+ private final Set<DexType> classesWithRemovedExtensionFields = Sets.newIdentityHashSet();
private final Set<DexField> removedExtensionFields = Sets.newIdentityHashSet();
GeneratedExtensionRegistryShrinker(
@@ -79,9 +90,63 @@
* Will be run after tree shaking. This populates the set {@link #removedExtensionFields}. This
* set is used by the member value propagation, which rewrites all reads of these fields by
* const-null.
+ *
+ * <p>For the second round of tree pruning, this method will return a non-default {@link
+ * TreePrunerConfiguration} that specifies that all fields that are only referenced from a {@code
+ * findLiteExtensionByNumber()} method should be removed. This is safe because we will revisit all
+ * of these methods and replace the reads of these fields by null.
*/
- public void run() {
- forEachDeadProtoExtensionField(removedExtensionFields::add);
+ public TreePrunerConfiguration run(Enqueuer.Mode mode) {
+ forEachDeadProtoExtensionField(this::recordDeadProtoExtensionField);
+ return createTreePrunerConfiguration(mode);
+ }
+
+ private void recordDeadProtoExtensionField(DexField field) {
+ classesWithRemovedExtensionFields.add(field.holder);
+ removedExtensionFields.add(field);
+ }
+
+ private TreePrunerConfiguration createTreePrunerConfiguration(Enqueuer.Mode mode) {
+ if (mode.isFinalTreeShaking()) {
+ return new DefaultTreePrunerConfiguration() {
+
+ @Override
+ public boolean isReachableOrReferencedField(
+ AppInfoWithLiveness appInfo, DexEncodedField field) {
+ return !wasRemoved(field.field) && super.isReachableOrReferencedField(appInfo, field);
+ }
+ };
+ }
+ return DefaultTreePrunerConfiguration.getInstance();
+ }
+
+ /**
+ * If {@param method} is a class initializer that initializes a dead proto extension field, then
+ * forcefully remove the field assignment and all the code that contributes to the initialization
+ * of the value of the field assignment.
+ */
+ public void rewriteCode(DexEncodedMethod method, IRCode code) {
+ if (method.isClassInitializer()
+ && classesWithRemovedExtensionFields.contains(method.method.holder)
+ && code.metadata().mayHaveStaticPut()) {
+ rewriteClassInitializer(code);
+ }
+ }
+
+ private void rewriteClassInitializer(IRCode code) {
+ List<StaticPut> toBeRemoved = new ArrayList<>();
+ for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
+ if (removedExtensionFields.contains(staticPut.getField())) {
+ toBeRemoved.add(staticPut);
+ }
+ }
+ for (StaticPut instruction : toBeRemoved) {
+ if (!instruction.hasBlock()) {
+ // Already removed.
+ continue;
+ }
+ IRCodeUtils.removeInstructionAndTransitiveInputsIfNotUsed(code, instruction);
+ }
}
public boolean wasRemoved(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
new file mode 100644
index 0000000..dfc3f0e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeUtils.java
@@ -0,0 +1,60 @@
+// 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.ir.code;
+
+import com.android.tools.r8.utils.DequeUtils;
+import com.google.common.collect.Sets;
+import java.util.Deque;
+import java.util.Set;
+
+public class IRCodeUtils {
+
+ /**
+ * Removes the given instruction and all the instructions that are used to define the in-values of
+ * the given instruction, even if the instructions may have side effects (!).
+ *
+ * <p>Use with caution!
+ */
+ public static void removeInstructionAndTransitiveInputsIfNotUsed(
+ IRCode code, Instruction instruction) {
+ Set<InstructionOrPhi> removed = Sets.newIdentityHashSet();
+ Deque<InstructionOrPhi> worklist = DequeUtils.newArrayDeque(instruction);
+ while (!worklist.isEmpty()) {
+ InstructionOrPhi instructionOrPhi = worklist.removeFirst();
+ if (removed.contains(instructionOrPhi)) {
+ // Already removed.
+ continue;
+ }
+ if (instructionOrPhi.isPhi()) {
+ Phi current = instructionOrPhi.asPhi();
+ if (!current.hasUsers() && !current.hasDebugUsers()) {
+ boolean hasOtherPhiUserThanSelf = false;
+ for (Phi phiUser : current.uniquePhiUsers()) {
+ if (phiUser != current) {
+ hasOtherPhiUserThanSelf = true;
+ break;
+ }
+ }
+ if (!hasOtherPhiUserThanSelf) {
+ current.removeDeadPhi();
+ for (Value operand : current.getOperands()) {
+ worklist.add(operand.isPhi() ? operand.asPhi() : operand.definition);
+ }
+ removed.add(current);
+ }
+ }
+ } else {
+ Instruction current = instructionOrPhi.asInstruction();
+ if (!current.hasOutValue() || !current.outValue().hasAnyUsers()) {
+ current.getBlock().listIterator(code, current).removeOrReplaceByDebugLocalRead();
+ for (Value inValue : current.inValues()) {
+ worklist.add(inValue.isPhi() ? inValue.asPhi() : inValue.definition);
+ }
+ removed.add(current);
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 1129616..8836361 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -604,6 +604,11 @@
return true;
}
+ @Override
+ public Instruction asInstruction() {
+ return this;
+ }
+
public boolean isArrayGet() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java b/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
index 10110a0..e9a5347 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionOrPhi.java
@@ -10,7 +10,15 @@
return false;
}
+ default Instruction asInstruction() {
+ return null;
+ }
+
default boolean isPhi() {
return false;
}
+
+ default Phi asPhi() {
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 47eb9d4..3252d67 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -224,11 +224,11 @@
return true;
}
- public void removeTrivialPhi() {
- removeTrivialPhi(null, null);
+ public boolean removeTrivialPhi() {
+ return removeTrivialPhi(null, null);
}
- public void removeTrivialPhi(IRBuilder builder, Set<Value> affectedValues) {
+ public boolean removeTrivialPhi(IRBuilder builder, Set<Value> affectedValues) {
Value same = null;
for (Value op : operands) {
if (op == same || op == this) {
@@ -238,7 +238,7 @@
if (same != null) {
// Merged at least two values and is therefore not trivial.
assert !isTrivialPhi();
- return;
+ return false;
}
same = op;
}
@@ -247,7 +247,7 @@
// When doing if-simplification we remove blocks and we can end up with cyclic phis
// of the form v1 = phi(v1, v1) in dead blocks. If we encounter that case we just
// leave the phi in there and check at the end that there are no trivial phis.
- return;
+ return false;
}
// Ensure that the value that replaces this phi is constrained to the type of this phi.
if (builder != null && typeLattice.isPreciseType() && !typeLattice.isBottom()) {
@@ -286,6 +286,7 @@
}
// Get rid of the phi itself.
block.removePhi(this);
+ return true;
}
public void removeDeadPhi() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 1e0a60d..6af3446 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -330,7 +330,6 @@
// Mapping from callee to the set of callers that were removed from the callee.
private Map<Node, Set<Node>> removedEdges = new IdentityHashMap<>();
- private int currentDepth = 0;
private int maxDepth = 0;
CycleEliminator(Collection<Node> nodes, InternalOptions options) {
@@ -345,10 +344,8 @@
CycleEliminationResult breakCycles() {
// Break cycles in this call graph by removing edges causing cycles.
- for (Node node : nodes) {
- assert currentDepth == 0;
- traverse(node);
- }
+ traverse();
+
CycleEliminationResult result = new CycleEliminationResult(removedEdges);
if (Log.ENABLED) {
Log.info(getClass(), "# call graph cycles broken: %s", result.numberOfRemovedEdges());
@@ -359,7 +356,6 @@
}
private void reset() {
- assert currentDepth == 0;
assert stack.isEmpty();
assert stackSet.isEmpty();
marked.clear();
@@ -367,41 +363,114 @@
removedEdges = new IdentityHashMap<>();
}
- private void traverse(Node node) {
- if (Log.ENABLED) {
- if (currentDepth > maxDepth) {
- maxDepth = currentDepth;
+ private static class WorkItem {
+ boolean isNode() {
+ return false;
+ }
+
+ NodeWorkItem asNode() {
+ return null;
+ }
+
+ boolean isIterator() {
+ return false;
+ }
+
+ IteratorWorkItem asIterator() {
+ return null;
+ }
+ }
+
+ private static class NodeWorkItem extends WorkItem {
+ private final Node node;
+
+ NodeWorkItem(Node node) {
+ this.node = node;
+ }
+
+ @Override
+ boolean isNode() {
+ return true;
+ }
+
+ @Override
+ NodeWorkItem asNode() {
+ return this;
+ }
+ }
+
+ private static class IteratorWorkItem extends WorkItem {
+ private final Node caller;
+ private final Iterator<Node> callees;
+
+ IteratorWorkItem(Node caller, Iterator<Node> callees) {
+ this.caller = caller;
+ this.callees = callees;
+ }
+
+ @Override
+ boolean isIterator() {
+ return true;
+ }
+
+ @Override
+ IteratorWorkItem asIterator() {
+ return this;
+ }
+ }
+
+ private void traverse() {
+ Deque<WorkItem> workItems = new ArrayDeque<>(nodes.size());
+ for (Node node : nodes) {
+ workItems.addLast(new NodeWorkItem(node));
+ }
+ while (!workItems.isEmpty()) {
+ WorkItem workItem = workItems.removeFirst();
+ if (workItem.isNode()) {
+ Node node = workItem.asNode().node;
+ if (Log.ENABLED) {
+ if (stack.size() > maxDepth) {
+ maxDepth = stack.size();
+ }
+ }
+
+ if (marked.contains(node)) {
+ // Already visited all nodes that can be reached from this node.
+ continue;
+ }
+
+ push(node);
+
+ // The callees must be sorted before calling traverse recursively. This ensures that
+ // cycles are broken the same way across multiple compilations.
+ Collection<Node> callees = node.getCalleesWithDeterministicOrder();
+
+ if (options.testing.nondeterministicCycleElimination) {
+ callees = reorderNodes(new ArrayList<>(callees));
+ }
+ workItems.addFirst(new IteratorWorkItem(node, callees.iterator()));
+ } else {
+ assert workItem.isIterator();
+ IteratorWorkItem iteratorWorkItem = workItem.asIterator();
+ Node newCaller = iterateCallees(iteratorWorkItem.callees, iteratorWorkItem.caller);
+ if (newCaller != null) {
+ // We did not finish the work on this iterator, so add it again.
+ workItems.addFirst(iteratorWorkItem);
+ workItems.addFirst(new NodeWorkItem(newCaller));
+ } else {
+ assert !iteratorWorkItem.callees.hasNext();
+ pop(iteratorWorkItem.caller);
+ marked.add(iteratorWorkItem.caller);
+ }
}
}
+ }
- if (marked.contains(node)) {
- // Already visited all nodes that can be reached from this node.
- return;
- }
-
- push(node);
-
- // The callees must be sorted before calling traverse recursively. This ensures that cycles
- // are broken the same way across multiple compilations.
- Collection<Node> callees = node.getCalleesWithDeterministicOrder();
-
- if (options.testing.nondeterministicCycleElimination) {
- callees = reorderNodes(new ArrayList<>(callees));
- }
-
- Iterator<Node> calleeIterator = callees.iterator();
+ private Node iterateCallees(Iterator<Node> calleeIterator, Node node) {
while (calleeIterator.hasNext()) {
Node callee = calleeIterator.next();
-
- // If we've exceeded the depth threshold, then treat it as if we have found a cycle. This
- // ensures that we won't run into stack overflows when the call graph contains large call
- // chains. This should have a negligible impact on code size as long as the threshold is
- // large enough.
boolean foundCycle = stackSet.contains(callee);
- boolean thresholdExceeded =
- currentDepth >= options.callGraphCycleEliminatorMaxDepthThreshold
- && edgeRemovalIsSafe(node, callee);
- if (foundCycle || thresholdExceeded) {
+ if (foundCycle) {
// Found a cycle that needs to be eliminated.
if (edgeRemovalIsSafe(node, callee)) {
// Break the cycle by removing the edge node->callee.
@@ -461,13 +530,10 @@
recoverStack(cycle);
}
} else {
- currentDepth++;
- traverse(callee);
- currentDepth--;
+ return callee;
}
}
- pop(node);
- marked.add(node);
+ return null;
}
private void push(Node node) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 6603015..28e95f8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
-import static it.unimi.dsi.fastutil.ints.Int2ObjectSortedMaps.emptyMap;
+import static it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMaps.emptyMap;
import com.android.tools.r8.cf.code.CfFrame;
import com.android.tools.r8.cf.code.CfFrame.FrameType;
@@ -36,9 +36,9 @@
import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOutputMode;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
-import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
@@ -136,10 +136,10 @@
public static final LocalVariableList EMPTY = new LocalVariableList(0, 0, emptyMap());
public final int startOffset;
public final int endOffset;
- public final Int2ObjectMap<DebugLocalInfo> locals;
+ public final Int2ReferenceMap<DebugLocalInfo> locals;
private LocalVariableList(
- int startOffset, int endOffset, Int2ObjectMap<DebugLocalInfo> locals) {
+ int startOffset, int endOffset, Int2ReferenceMap<DebugLocalInfo> locals) {
this.startOffset = startOffset;
this.endOffset = endOffset;
this.locals = locals;
@@ -151,7 +151,7 @@
Reference2IntMap<CfLabel> labelOffsets) {
int startOffset = Integer.MIN_VALUE;
int endOffset = Integer.MAX_VALUE;
- Int2ObjectMap<DebugLocalInfo> currentLocals = null;
+ Int2ReferenceMap<DebugLocalInfo> currentLocals = null;
for (LocalVariableInfo local : locals) {
int start = labelOffsets.getInt(local.getStart());
int end = labelOffsets.getInt(local.getEnd());
@@ -163,7 +163,7 @@
continue;
}
if (currentLocals == null) {
- currentLocals = new Int2ObjectOpenHashMap<>();
+ currentLocals = new Int2ReferenceOpenHashMap<>();
}
startOffset = Math.max(startOffset, start);
endOffset = Math.min(endOffset, end);
@@ -181,17 +181,17 @@
return locals.get(register);
}
- public Int2ObjectOpenHashMap<DebugLocalInfo> merge(LocalVariableList other) {
+ public Int2ReferenceOpenHashMap<DebugLocalInfo> merge(LocalVariableList other) {
return merge(this, other);
}
- private static Int2ObjectOpenHashMap<DebugLocalInfo> merge(
+ private static Int2ReferenceOpenHashMap<DebugLocalInfo> merge(
LocalVariableList a, LocalVariableList b) {
if (a.locals.size() > b.locals.size()) {
return merge(b, a);
}
- Int2ObjectOpenHashMap<DebugLocalInfo> result = new Int2ObjectOpenHashMap<>();
- for (Entry<DebugLocalInfo> local : a.locals.int2ObjectEntrySet()) {
+ Int2ReferenceOpenHashMap<DebugLocalInfo> result = new Int2ReferenceOpenHashMap<>();
+ for (Entry<DebugLocalInfo> local : a.locals.int2ReferenceEntrySet()) {
if (local.getValue().equals(b.getLocal(local.getIntKey()))) {
result.put(local.getIntKey(), local.getValue());
}
@@ -212,8 +212,8 @@
private LocalVariableList cachedLocalVariableList;
private int currentInstructionIndex;
private boolean inPrelude;
- private Int2ObjectMap<DebugLocalInfo> incomingLocals;
- private Int2ObjectMap<DebugLocalInfo> outgoingLocals;
+ private Int2ReferenceMap<DebugLocalInfo> incomingLocals;
+ private Int2ReferenceMap<DebugLocalInfo> outgoingLocals;
private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
private final CanonicalPositions canonicalPositions;
private final InternalOutputMode internalOutputMode;
@@ -385,7 +385,7 @@
setLocalVariableLists();
DexSourceCode.buildArgumentsWithUnusedArgumentStubs(builder, 0, method, state::write);
// Add debug information for all locals at the initial label.
- Int2ObjectMap<DebugLocalInfo> locals = getLocalVariables(0).locals;
+ Int2ReferenceMap<DebugLocalInfo> locals = getLocalVariables(0).locals;
if (!locals.isEmpty()) {
int firstLocalIndex = 0;
if (!method.isStatic()) {
@@ -397,7 +397,7 @@
firstLocalIndex++;
}
}
- for (Entry<DebugLocalInfo> entry : locals.int2ObjectEntrySet()) {
+ for (Entry<DebugLocalInfo> entry : locals.int2ReferenceEntrySet()) {
if (firstLocalIndex <= entry.getIntKey()) {
builder.addDebugLocalStart(entry.getIntKey(), entry.getValue());
}
@@ -470,16 +470,16 @@
getCanonicalDebugPositionAtOffset(isExceptional ? successorOffset : predecessorOffset));
// Manually compute the local variable change for the block transfer.
- Int2ObjectMap<DebugLocalInfo> atSource = getLocalVariables(predecessorOffset).locals;
- Int2ObjectMap<DebugLocalInfo> atTarget = getLocalVariables(successorOffset).locals;
+ Int2ReferenceMap<DebugLocalInfo> atSource = getLocalVariables(predecessorOffset).locals;
+ Int2ReferenceMap<DebugLocalInfo> atTarget = getLocalVariables(successorOffset).locals;
if (!isExceptional) {
- for (Entry<DebugLocalInfo> entry : atSource.int2ObjectEntrySet()) {
+ for (Entry<DebugLocalInfo> entry : atSource.int2ReferenceEntrySet()) {
if (atTarget.get(entry.getIntKey()) != entry.getValue()) {
builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
}
}
}
- for (Entry<DebugLocalInfo> entry : atTarget.int2ObjectEntrySet()) {
+ for (Entry<DebugLocalInfo> entry : atTarget.int2ReferenceEntrySet()) {
if (atSource.get(entry.getIntKey()) != entry.getValue()) {
builder.addDebugLocalStart(entry.getIntKey(), entry.getValue());
}
@@ -492,7 +492,7 @@
// back-edge will explicitly keep locals live at that point.
if (!hasExitingInstruction && code.getInstructions().get(predecessorOffset) instanceof CfGoto) {
assert !isExceptional;
- for (Entry<DebugLocalInfo> entry : atSource.int2ObjectEntrySet()) {
+ for (Entry<DebugLocalInfo> entry : atSource.int2ReferenceEntrySet()) {
if (atTarget.get(entry.getIntKey()) == entry.getValue()) {
builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
}
@@ -549,7 +549,7 @@
for (int successorOffset : currentBlockInfo.exceptionalSuccessors) {
live.putAll(getLocalVariables(successorOffset).locals);
}
- for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+ for (Entry<DebugLocalInfo> entry : incomingLocals.int2ReferenceEntrySet()) {
if (live.get(entry.getIntKey()) != entry.getValue()) {
builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
}
@@ -687,7 +687,7 @@
private void endLocals(IRBuilder builder) {
assert localsChanged();
- for (Entry<DebugLocalInfo> entry : incomingLocals.int2ObjectEntrySet()) {
+ for (Entry<DebugLocalInfo> entry : incomingLocals.int2ReferenceEntrySet()) {
if (!entry.getValue().equals(outgoingLocals.get(entry.getIntKey()))) {
builder.addDebugLocalEnd(entry.getIntKey(), entry.getValue());
}
@@ -696,7 +696,7 @@
private void startLocals(IRBuilder builder) {
assert localsChanged();
- for (Entry<DebugLocalInfo> entry : outgoingLocals.int2ObjectEntrySet()) {
+ for (Entry<DebugLocalInfo> entry : outgoingLocals.int2ReferenceEntrySet()) {
if (!entry.getValue().equals(incomingLocals.get(entry.getIntKey()))) {
Slot slot = state.read(entry.getIntKey());
if (slot != null && slot.type != ValueType.fromDexType(entry.getValue().type)) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 358a7fe..9935044 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -939,8 +939,6 @@
count++;
result = appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(name));
} while (appView.definitionFor(result) != null);
- // Register the newly generated type in the subtyping hierarchy, if we have one.
- appView.appInfo().registerNewType(result, appView.dexItemFactory().objectType);
return result;
}
@@ -1154,6 +1152,10 @@
previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
+ appView.withGeneratedExtensionRegistryShrinker(shrinker -> shrinker.rewriteCode(method, code));
+
+ previous = printMethod(code, "IR after generated extension registry shrinking (SSA)", previous);
+
appView.withGeneratedMessageLiteShrinker(shrinker -> shrinker.run(method, code));
previous = printMethod(code, "IR after generated message lite shrinking (SSA)", previous);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 5edd98e..d52f02e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -49,7 +49,6 @@
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
@@ -253,8 +252,7 @@
if (!appView.options().encodeChecksums) {
return DexProgramClass::invalidChecksumRequest;
}
- long hash = Objects.hash(method, method.getCode());
- return c -> hash;
+ return c -> method.method.hashCode();
}
private MethodProvider getMethodProviderOrNull(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 40932cb..9998c94 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -105,9 +105,6 @@
lambdaClassType,
factory.createProto(lambdaClassType, descriptor.captures.values),
rewriter.createInstanceMethodName);
-
- // We have to register this new class as a subtype of object.
- rewriter.converter.appView.appInfo().registerNewType(type, factory.objectType);
}
// Generate unique lambda class type for lambda descriptor and instantiation point context.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index cbedf91..ab931cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -23,7 +23,7 @@
import com.android.tools.r8.ir.conversion.CodeOptimization;
import com.android.tools.r8.ir.conversion.PostOptimization;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.ir.optimize.info.MutableCallSiteOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.ConcreteCallSiteOptimizationInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.Sets;
@@ -80,9 +80,6 @@
if (!instruction.isInvokeMethod() && !instruction.isInvokeCustom()) {
continue;
}
- if (!MutableCallSiteOptimizationInfo.hasArgumentsToRecord(instruction.inValues())) {
- continue;
- }
if (instruction.isInvokeMethod()) {
InvokeMethod invoke = instruction.asInvokeMethod();
if (invoke.isInvokeMethodWithDynamicDispatch()) {
@@ -102,7 +99,7 @@
continue;
}
for (DexEncodedMethod target : targets) {
- recordArgumentsIfNecessary(context, target, invoke.inValues());
+ recordArgumentsIfNecessary(target, invoke.inValues());
}
}
// TODO(b/129458850): if lambda desugaring happens before IR processing, seeing invoke-custom
@@ -116,47 +113,62 @@
continue;
}
for (DexEncodedMethod target : targets) {
- recordArgumentsIfNecessary(context, target, instruction.inValues());
+ recordArgumentsIfNecessary(target, instruction.inValues());
}
}
}
}
- private void recordArgumentsIfNecessary(
- DexEncodedMethod context, DexEncodedMethod target, List<Value> inValues) {
+ // Record arguments for the given method if necessary.
+ // At the same time, if it decides to bail out, make the corresponding info immutable so that we
+ // can avoid recording arguments for the same method accidentally.
+ private void recordArgumentsIfNecessary(DexEncodedMethod target, List<Value> inValues) {
assert !target.isObsolete();
- if (target.shouldNotHaveCode() || target.method.getArity() == 0) {
+ if (target.getCallSiteOptimizationInfo().isTop()) {
return;
}
+ target.joinCallSiteOptimizationInfo(
+ computeCallSiteOptimizationInfoFromArguments(target, inValues), appView);
+ }
+
+ private CallSiteOptimizationInfo computeCallSiteOptimizationInfoFromArguments(
+ DexEncodedMethod target, List<Value> inValues) {
+ // No method body or no argument at all.
+ if (target.shouldNotHaveCode() || inValues.size() == 0) {
+ return CallSiteOptimizationInfo.TOP;
+ }
// If pinned, that method could be invoked via reflection.
if (appView.appInfo().isPinned(target.method)) {
- return;
+ return CallSiteOptimizationInfo.TOP;
}
// If the method overrides a library method, it is unsure how the method would be invoked by
// that library.
if (target.isLibraryMethodOverride().isTrue()) {
- return;
+ return CallSiteOptimizationInfo.TOP;
}
// If the program already has illegal accesses, method resolution results will reflect that too.
// We should avoid recording arguments in that case. E.g., b/139823850: static methods can be a
// result of virtual call targets, if that's the only method that matches name and signature.
int argumentOffset = target.isStatic() ? 0 : 1;
if (inValues.size() != argumentOffset + target.method.getArity()) {
- return;
+ return CallSiteOptimizationInfo.BOTTOM;
}
- MutableCallSiteOptimizationInfo optimizationInfo =
- target.getMutableCallSiteOptimizationInfo(appView);
- optimizationInfo.recordArguments(appView, context, inValues);
+ return ConcreteCallSiteOptimizationInfo.fromArguments(appView, target, inValues);
}
// If collected call site optimization info has something useful, e.g., non-null argument,
// insert corresponding assume instructions for arguments.
public void applyCallSiteOptimizationInfo(
IRCode code, CallSiteOptimizationInfo callSiteOptimizationInfo) {
- if (mode != Mode.REVISIT
- || !callSiteOptimizationInfo.hasUsefulOptimizationInfo(appView, code.method)) {
+ if (mode != Mode.REVISIT) {
return;
}
+ // TODO(b/139246447): Assert no BOTTOM left.
+ if (!callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()) {
+ return;
+ }
+ assert callSiteOptimizationInfo.asConcreteCallSiteOptimizationInfo()
+ .hasUsefulOptimizationInfo(appView, code.method);
Set<Value> affectedValues = Sets.newIdentityHashSet();
List<Assume<?>> assumeInstructions = new LinkedList<>();
List<ConstInstruction> constants = new LinkedList<>();
@@ -237,15 +249,18 @@
for (DexProgramClass clazz : appView.appInfo().classes()) {
for (DexEncodedMethod method : clazz.methods()) {
assert !method.isObsolete();
- if (method.shouldNotHaveCode()
- || method.getCallSiteOptimizationInfo().isDefaultCallSiteOptimizationInfo()) {
+ if (method.shouldNotHaveCode()) {
+ assert !method.hasCode();
continue;
}
- MutableCallSiteOptimizationInfo optimizationInfo =
- method.getCallSiteOptimizationInfo().asMutableCallSiteOptimizationInfo();
- if (optimizationInfo.hasUsefulOptimizationInfo(appView, method)) {
- targetsToRevisit.add(method);
+ // TODO(b/139246447): Assert no BOTTOM left.
+ CallSiteOptimizationInfo callSiteOptimizationInfo = method.getCallSiteOptimizationInfo();
+ if (!callSiteOptimizationInfo.isConcreteCallSiteOptimizationInfo()) {
+ continue;
}
+ assert callSiteOptimizationInfo.asConcreteCallSiteOptimizationInfo()
+ .hasUsefulOptimizationInfo(appView, method);
+ targetsToRevisit.add(method);
}
}
if (revisitedMethods != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index f2c9031..ccf312a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -98,8 +98,8 @@
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -460,7 +460,7 @@
public static class SwitchBuilder extends InstructionBuilder<SwitchBuilder> {
private Value value;
- private final Int2ObjectSortedMap<BasicBlock> keyToTarget = new Int2ObjectAVLTreeMap<>();
+ private final Int2ReferenceSortedMap<BasicBlock> keyToTarget = new Int2ReferenceAVLTreeMap<>();
private BasicBlock fallthrough;
public SwitchBuilder(Position position) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 7f055de..2ac3abd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -806,13 +806,13 @@
includeInstruction(instruction);
// Check if this instruction ends the outline.
if (actualInstructions >= appView.options().outline.maxSize) {
- candidate(start, index + 1);
+ candidate(start, index + 1, actualInstructions);
} else {
index++;
}
} else if (index > start) {
// Do not add this instruction, candidate ends with previous instruction.
- candidate(start, index);
+ candidate(start, index, actualInstructions);
} else {
// Restart search from next instruction.
reset(index + 1);
@@ -1080,7 +1080,7 @@
protected abstract void handle(int start, int end, Outline outline);
- private void candidate(int start, int index) {
+ private void candidate(int start, int index, int actualInstructions) {
List<Instruction> instructions = getInstructionArray();
assert !instructions.get(start).isConstInstruction();
@@ -1100,13 +1100,7 @@
}
// Check if the candidate qualifies.
- int nonConstInstructions = 0;
- for (int i = start; i < end; i++) {
- if (!instructions.get(i).isConstInstruction()) {
- nonConstInstructions++;
- }
- }
- if (nonConstInstructions < appView.options().outline.minSize) {
+ if (actualInstructions < appView.options().outline.minSize) {
reset(start + 1);
return;
}
@@ -1359,7 +1353,6 @@
// No need to sort the direct methods as they are generated in sorted order.
// Build the outliner class.
- DexType superType = appView.dexItemFactory().createType("Ljava/lang/Object;");
DexTypeList interfaces = DexTypeList.empty();
DexString sourceFile = appView.dexItemFactory().createString("outline");
ClassAccessFlags accessFlags = ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC);
@@ -1371,7 +1364,7 @@
null,
new SynthesizedOrigin("outlining", getClass()),
accessFlags,
- superType,
+ appView.dexItemFactory().objectType,
interfaces,
sourceFile,
null,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java
new file mode 100644
index 0000000..20ee5df
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/BottomCallSiteOptimizationInfo.java
@@ -0,0 +1,16 @@
+// 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.ir.optimize.info;
+
+// Nothing is known about arguments at call sites.
+public class BottomCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
+ static BottomCallSiteOptimizationInfo INSTANCE = new BottomCallSiteOptimizationInfo();
+
+ private BottomCallSiteOptimizationInfo() {}
+
+ @Override
+ public boolean isBottom() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index d82f6c5..c99f24c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -8,24 +8,44 @@
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
+// A flat lattice structure:
+// BOTTOM, TOP, and a lattice element that holds accumulated argument info.
public abstract class CallSiteOptimizationInfo {
+ public static BottomCallSiteOptimizationInfo BOTTOM = BottomCallSiteOptimizationInfo.INSTANCE;
+ public static TopCallSiteOptimizationInfo TOP = TopCallSiteOptimizationInfo.INSTANCE;
- public boolean isDefaultCallSiteOptimizationInfo() {
+ public boolean isBottom() {
return false;
}
- public DefaultCallSiteOptimizationInfo asDefaultCallSiteOptimizationInfo() {
- return null;
- }
-
- public boolean isMutableCallSiteOptimizationInfo() {
+ public boolean isTop() {
return false;
}
- public MutableCallSiteOptimizationInfo asMutableCallSiteOptimizationInfo() {
+ public boolean isConcreteCallSiteOptimizationInfo() {
+ return false;
+ }
+
+ public ConcreteCallSiteOptimizationInfo asConcreteCallSiteOptimizationInfo() {
return null;
}
+ public CallSiteOptimizationInfo join(
+ CallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod encodedMethod) {
+ if (isBottom()) {
+ return other;
+ }
+ if (other.isBottom()) {
+ return this;
+ }
+ if (isTop() || other.isTop()) {
+ return TOP;
+ }
+ assert isConcreteCallSiteOptimizationInfo() && other.isConcreteCallSiteOptimizationInfo();
+ return asConcreteCallSiteOptimizationInfo()
+ .join(other.asConcreteCallSiteOptimizationInfo(), appView, encodedMethod);
+ }
+
/**
* {@link CallSiteOptimizationInfoPropagator} will reprocess the call target if its collected call
* site optimization info has something useful that can trigger more optimizations. For example,
@@ -37,7 +57,9 @@
}
// The index exactly matches with in values of invocation, i.e., even including receiver.
- public abstract TypeLatticeElement getDynamicUpperBoundType(int argIndex);
+ public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
+ return null;
+ }
// TODO(b/139246447): dynamic lower bound type?
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
new file mode 100644
index 0000000..3dc5875
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -0,0 +1,167 @@
+// 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.ir.optimize.info;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.Value;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
+import java.util.List;
+
+// Accumulated optimization info from call sites.
+public class ConcreteCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
+
+ // inValues() size == DexMethod.arity + (isStatic ? 0 : 1) // receiver
+ // That is, this information takes into account the receiver as well.
+ private final int size;
+ private final Int2ReferenceArrayMap<TypeLatticeElement> dynamicUpperBoundTypes;
+
+ private ConcreteCallSiteOptimizationInfo(DexEncodedMethod encodedMethod) {
+ assert encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1) > 0;
+ this.size = encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1);
+ this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
+ }
+
+ private ConcreteCallSiteOptimizationInfo(int size) {
+ this.size = size;
+ this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
+ }
+
+ CallSiteOptimizationInfo join(
+ ConcreteCallSiteOptimizationInfo other, AppView<?> appView, DexEncodedMethod encodedMethod) {
+ assert this.size == other.size;
+ ConcreteCallSiteOptimizationInfo result = new ConcreteCallSiteOptimizationInfo(this.size);
+ assert result.dynamicUpperBoundTypes != null;
+ for (int i = 0; i < result.size; i++) {
+ TypeLatticeElement thisUpperBoundType = getDynamicUpperBoundType(i);
+ if (thisUpperBoundType == null) {
+ // This means the corresponding argument is primitive. The counterpart should be too.
+ assert other.getDynamicUpperBoundType(i) == null;
+ continue;
+ }
+ assert thisUpperBoundType.isReference();
+ TypeLatticeElement otherUpperBoundType = other.getDynamicUpperBoundType(i);
+ assert otherUpperBoundType != null && otherUpperBoundType.isReference();
+ result.dynamicUpperBoundTypes.put(
+ i, thisUpperBoundType.join(otherUpperBoundType, appView));
+ }
+ if (result.hasUsefulOptimizationInfo(appView, encodedMethod)) {
+ return result;
+ }
+ // As soon as we know the argument collection so far does not have any useful optimization info,
+ // move to TOP so that further collection can be simply skipped.
+ return TOP;
+ }
+
+ private TypeLatticeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod encodedMethod) {
+ int argOffset = encodedMethod.isStatic() ? 0 : 1;
+ int size = encodedMethod.method.getArity() + argOffset;
+ TypeLatticeElement[] staticTypes = new TypeLatticeElement[size];
+ if (!encodedMethod.isStatic()) {
+ staticTypes[0] =
+ TypeLatticeElement.fromDexType(
+ encodedMethod.method.holder, definitelyNotNull(), appView);
+ }
+ for (int i = 0; i < encodedMethod.method.getArity(); i++) {
+ staticTypes[i + argOffset] =
+ TypeLatticeElement.fromDexType(
+ encodedMethod.method.proto.parameters.values[i], maybeNull(), appView);
+ }
+ return staticTypes;
+ }
+
+ @Override
+ public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
+ TypeLatticeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
+ for (int i = 0; i < size; i++) {
+ if (!staticTypes[i].isReference()) {
+ continue;
+ }
+ TypeLatticeElement dynamicUpperBoundType = getDynamicUpperBoundType(i);
+ if (dynamicUpperBoundType == null) {
+ continue;
+ }
+ // To avoid the full join of type lattices below, separately check if the nullability of
+ // arguments is improved, and if so, we can eagerly conclude that we've collected useful
+ // call site information for this method.
+ Nullability nullability = dynamicUpperBoundType.nullability();
+ if (nullability.isDefinitelyNull()) {
+ return true;
+ }
+ // TODO(b/139246447): Similar to nullability, if dynamic lower bound type is available,
+ // we stop here and regard that call sites of this method have useful info.
+ // In general, though, we're looking for (strictly) better dynamic types for arguments.
+ if (dynamicUpperBoundType.strictlyLessThan(staticTypes[i], appView)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
+ assert 0 <= argIndex && argIndex < size;
+ return dynamicUpperBoundTypes.getOrDefault(argIndex, null);
+ }
+
+ public static CallSiteOptimizationInfo fromArguments(
+ AppView<? extends AppInfoWithSubtyping> appView,
+ DexEncodedMethod method,
+ List<Value> inValues) {
+ ConcreteCallSiteOptimizationInfo newCallSiteInfo = new ConcreteCallSiteOptimizationInfo(method);
+ assert newCallSiteInfo.size == inValues.size();
+ for (int i = 0; i < newCallSiteInfo.size; i++) {
+ Value arg = inValues.get(i);
+ // TODO(b/69963623): may need different place to store constants.
+ if (arg.getTypeLattice().isPrimitive()) {
+ continue;
+ }
+ assert arg.getTypeLattice().isReference();
+ newCallSiteInfo.dynamicUpperBoundTypes.put(i, arg.getDynamicUpperBoundType(appView));
+ }
+ if (newCallSiteInfo.hasUsefulOptimizationInfo(appView, method)) {
+ return newCallSiteInfo;
+ }
+ // As soon as we know the current call site does not have any useful optimization info,
+ // return TOP so that further collection can be simply skipped.
+ return TOP;
+ }
+
+ @Override
+ public boolean isConcreteCallSiteOptimizationInfo() {
+ return true;
+ }
+
+ @Override
+ public ConcreteCallSiteOptimizationInfo asConcreteCallSiteOptimizationInfo() {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof ConcreteCallSiteOptimizationInfo)) {
+ return false;
+ }
+ ConcreteCallSiteOptimizationInfo otherInfo = (ConcreteCallSiteOptimizationInfo) other;
+ assert this.dynamicUpperBoundTypes != null;
+ return this.dynamicUpperBoundTypes.equals(otherInfo.dynamicUpperBoundTypes);
+ }
+
+ @Override
+ public int hashCode() {
+ assert this.dynamicUpperBoundTypes != null;
+ return System.identityHashCode(dynamicUpperBoundTypes);
+ }
+
+ @Override
+ public String toString() {
+ return dynamicUpperBoundTypes.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultCallSiteOptimizationInfo.java
deleted file mode 100644
index e67ec20..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultCallSiteOptimizationInfo.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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.ir.optimize.info;
-
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-
-public class DefaultCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
-
- private static final DefaultCallSiteOptimizationInfo INSTANCE =
- new DefaultCallSiteOptimizationInfo();
-
- private DefaultCallSiteOptimizationInfo() {}
-
- public static DefaultCallSiteOptimizationInfo getInstance() {
- return INSTANCE;
- }
-
- @Override
- public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
- return null;
- }
-
- @Override
- public boolean isDefaultCallSiteOptimizationInfo() {
- return true;
- }
-
- @Override
- public DefaultCallSiteOptimizationInfo asDefaultCallSiteOptimizationInfo() {
- return this;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableCallSiteOptimizationInfo.java
deleted file mode 100644
index c18624e..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableCallSiteOptimizationInfo.java
+++ /dev/null
@@ -1,256 +0,0 @@
-// 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.ir.optimize.info;
-
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.code.Value;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class MutableCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
-
- // inValues() size == DexMethod.arity + (isStatic ? 0 : 1) // receiver
- // That is, this information takes into account the receiver as well.
- private final int size;
- // Mappings from the calling context to argument collection. Note that, even in the same context,
- // the corresponding method can be invoked multiple times with different arguments, hence join of
- // argument collections.
- private final Map<DexEncodedMethod, ArgumentCollection> callSiteInfos = new ConcurrentHashMap<>();
- private ArgumentCollection cachedRepresentative = null;
-
- private static class ArgumentCollection {
-
- private final int size;
- private final Int2ReferenceArrayMap<TypeLatticeElement> dynamicUpperBoundTypes;
-
- private static final ArgumentCollection BOTTOM = new ArgumentCollection() {
- @Override
- public int hashCode() {
- return System.identityHashCode(this);
- }
-
- @Override
- public String toString() {
- return "(BOTTOM)";
- }
- };
-
- // Only used to create a canonical BOTTOM.
- private ArgumentCollection() {
- this.size = -1;
- this.dynamicUpperBoundTypes = null;
- }
-
- ArgumentCollection(int size) {
- this.size = size;
- this.dynamicUpperBoundTypes = new Int2ReferenceArrayMap<>(size);
- }
-
- TypeLatticeElement getDynamicUpperBoundType(int index) {
- assert dynamicUpperBoundTypes != null;
- assert 0 <= index && index < size;
- return dynamicUpperBoundTypes.getOrDefault(index, null);
- }
-
- ArgumentCollection join(ArgumentCollection other, AppView<?> appView) {
- if (other == BOTTOM) {
- return this;
- }
- if (this == BOTTOM) {
- return other;
- }
- assert this.size == other.size;
- ArgumentCollection result = new ArgumentCollection(this.size);
- assert result.dynamicUpperBoundTypes != null;
- for (int i = 0; i < result.size; i++) {
- TypeLatticeElement thisUpperBoundType = this.getDynamicUpperBoundType(i);
- if (thisUpperBoundType == null) {
- // This means the corresponding argument is primitive. The counterpart should be too.
- assert other.getDynamicUpperBoundType(i) == null;
- continue;
- }
- assert thisUpperBoundType.isReference();
- TypeLatticeElement otherUpperBoundType = other.getDynamicUpperBoundType(i);
- assert otherUpperBoundType != null && otherUpperBoundType.isReference();
- result.dynamicUpperBoundTypes.put(
- i, thisUpperBoundType.join(otherUpperBoundType, appView));
- }
- return result;
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof ArgumentCollection)) {
- return false;
- }
- ArgumentCollection otherCollection = (ArgumentCollection) other;
- if (this == BOTTOM || otherCollection == BOTTOM) {
- return this == BOTTOM && otherCollection == BOTTOM;
- }
- assert this.dynamicUpperBoundTypes != null;
- return this.dynamicUpperBoundTypes.equals(otherCollection.dynamicUpperBoundTypes);
- }
-
- @Override
- public int hashCode() {
- assert this.dynamicUpperBoundTypes != null;
- return System.identityHashCode(dynamicUpperBoundTypes);
- }
-
- @Override
- public String toString() {
- assert this.dynamicUpperBoundTypes != null;
- return dynamicUpperBoundTypes.toString();
- }
- }
-
- public MutableCallSiteOptimizationInfo(DexEncodedMethod encodedMethod) {
- assert encodedMethod.method.getArity() > 0;
- this.size = encodedMethod.method.getArity() + (encodedMethod.isStatic() ? 0 : 1);
- }
-
- private void computeCachedRepresentativeIfNecessary(AppView<?> appView) {
- if (cachedRepresentative == null && !callSiteInfos.isEmpty()) {
- synchronized (callSiteInfos) {
- // Make sure collected information is not flushed out by other threads.
- if (!callSiteInfos.isEmpty()) {
- cachedRepresentative =
- callSiteInfos.values().stream()
- .reduce(
- ArgumentCollection.BOTTOM,
- (prev, next) -> prev.join(next, appView),
- (prev, next) -> prev.join(next, appView));
- // After creating a cached representative, flush out the collected information.
- callSiteInfos.clear();
- } else {
- // If collected information is gone while waiting for the lock, make sure it's used to
- // compute the cached representative.
- assert cachedRepresentative != null;
- }
- }
- }
- }
-
- private TypeLatticeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod encodedMethod) {
- int argOffset = encodedMethod.isStatic() ? 0 : 1;
- int size = encodedMethod.method.getArity() + argOffset;
- TypeLatticeElement[] staticTypes = new TypeLatticeElement[size];
- if (!encodedMethod.isStatic()) {
- staticTypes[0] =
- TypeLatticeElement.fromDexType(
- encodedMethod.method.holder, definitelyNotNull(), appView);
- }
- for (int i = 0; i < encodedMethod.method.getArity(); i++) {
- staticTypes[i + argOffset] =
- TypeLatticeElement.fromDexType(
- encodedMethod.method.proto.parameters.values[i], maybeNull(), appView);
- }
- return staticTypes;
- }
-
- @Override
- public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
- computeCachedRepresentativeIfNecessary(appView);
- TypeLatticeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
- for (int i = 0; i < size; i++) {
- if (!staticTypes[i].isReference()) {
- continue;
- }
- TypeLatticeElement dynamicUpperBoundType = getDynamicUpperBoundType(i);
- if (dynamicUpperBoundType == null) {
- continue;
- }
- // To avoid the full join of type lattices below, separately check if the nullability of
- // arguments is improved, and if so, we can eagerly conclude that we've collected useful
- // call site information for this method.
- Nullability nullability = dynamicUpperBoundType.nullability();
- if (nullability.isDefinitelyNull()) {
- return true;
- }
- // TODO(b/139246447): Similar to nullability, if dynamic lower bound type is available,
- // we stop here and regard that call sites of this method have useful info.
- // In general, though, we're looking for (strictly) better dynamic types for arguments.
- if (dynamicUpperBoundType.strictlyLessThan(staticTypes[i], appView)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
- assert 0 <= argIndex && argIndex < size;
- if (cachedRepresentative == null) {
- return null;
- }
- return cachedRepresentative.getDynamicUpperBoundType(argIndex);
- }
-
- public static boolean hasArgumentsToRecord(List<Value> inValues) {
- // TODO(b/69963623): allow primitive types with compile-time constants.
- for (Value v : inValues) {
- if (v.getTypeLattice().isReference()) {
- return true;
- }
- }
- return false;
- }
-
- public void recordArguments(
- AppView<? extends AppInfoWithSubtyping> appView,
- DexEncodedMethod callingContext,
- List<Value> inValues) {
- assert cachedRepresentative == null;
- assert size == inValues.size();
- ArgumentCollection newCallSiteInfo = new ArgumentCollection(size);
- for (int i = 0; i < size; i++) {
- Value arg = inValues.get(i);
- // TODO(b/69963623): may need different place to store constants.
- if (arg.getTypeLattice().isPrimitive()) {
- continue;
- }
- assert arg.getTypeLattice().isReference();
- newCallSiteInfo.dynamicUpperBoundTypes.put(i, arg.getDynamicUpperBoundType(appView));
- }
- assert callingContext != null;
- ArgumentCollection accumulatedArgumentCollection =
- callSiteInfos.computeIfAbsent(callingContext, ignore -> ArgumentCollection.BOTTOM);
- callSiteInfos.put(
- callingContext, accumulatedArgumentCollection.join(newCallSiteInfo, appView));
- }
-
- @Override
- public boolean isMutableCallSiteOptimizationInfo() {
- return true;
- }
-
- @Override
- public MutableCallSiteOptimizationInfo asMutableCallSiteOptimizationInfo() {
- return this;
- }
-
- @Override
- public String toString() {
- if (cachedRepresentative != null) {
- return cachedRepresentative.toString();
- }
- StringBuilder builder = new StringBuilder();
- for (Map.Entry<DexEncodedMethod, ArgumentCollection> entry : callSiteInfos.entrySet()) {
- builder.append(entry.getKey().toSourceString());
- builder.append(" -> ");
- builder.append(entry.getValue().toString());
- builder.append(System.lineSeparator());
- }
- return builder.toString();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java
new file mode 100644
index 0000000..5e1fdd8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/TopCallSiteOptimizationInfo.java
@@ -0,0 +1,16 @@
+// 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.ir.optimize.info;
+
+// Nothing should not be assumed about arguments at call sites.
+class TopCallSiteOptimizationInfo extends CallSiteOptimizationInfo {
+ static TopCallSiteOptimizationInfo INSTANCE = new TopCallSiteOptimizationInfo();
+
+ private TopCallSiteOptimizationInfo() {}
+
+ @Override
+ public boolean isTop() {
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 57edf39..a165998 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -384,9 +384,6 @@
group.compact();
DexProgramClass lambdaGroupClass = group.synthesizeClass(appView.options());
result.put(group, lambdaGroupClass);
-
- // We have to register this new class as a subtype of object.
- appView.appInfo().registerNewType(lambdaGroupClass.type, lambdaGroupClass.superType);
}
return result;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultTreePrunerConfiguration.java b/src/main/java/com/android/tools/r8/shaking/DefaultTreePrunerConfiguration.java
new file mode 100644
index 0000000..c33ec55
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultTreePrunerConfiguration.java
@@ -0,0 +1,24 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexEncodedField;
+
+public class DefaultTreePrunerConfiguration implements TreePrunerConfiguration {
+
+ private static final DefaultTreePrunerConfiguration INSTANCE =
+ new DefaultTreePrunerConfiguration();
+
+ public DefaultTreePrunerConfiguration() {}
+
+ public static DefaultTreePrunerConfiguration getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isReachableOrReferencedField(AppInfoWithLiveness appInfo, DexEncodedField field) {
+ return appInfo.isFieldRead(field) || appInfo.isFieldWritten(field);
+ }
+}
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 183151c..680cdf8 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
import com.android.tools.r8.ir.code.ArrayPut;
@@ -581,488 +582,497 @@
return isRead ? info.recordRead(field, context) : info.recordWrite(field, context);
}
- private class UseRegistry extends com.android.tools.r8.graph.UseRegistry {
+ void traceCallSite(DexCallSite callSite, DexEncodedMethod currentMethod) {
+ callSites.add(callSite);
- private final DexProgramClass currentHolder;
- private final DexEncodedMethod currentMethod;
-
- private UseRegistry(DexItemFactory factory, DexProgramClass holder, DexEncodedMethod method) {
- super(factory);
- assert holder.type == method.method.holder;
- this.currentHolder = holder;
- this.currentMethod = method;
- }
-
- private KeepReasonWitness reportClassReferenced(DexProgramClass referencedClass) {
- return graphReporter.reportClassReferencedFrom(referencedClass, currentMethod);
- }
-
- @Override
- public boolean registerInvokeVirtual(DexMethod method) {
- return registerInvokeVirtual(method, KeepReason.invokedFrom(currentHolder, currentMethod));
- }
-
- boolean registerInvokeVirtual(DexMethod method, KeepReason keepReason) {
- if (method == appView.dexItemFactory().classMethods.newInstance
- || method == appView.dexItemFactory().constructorMethods.newInstance) {
- pendingReflectiveUses.add(currentMethod);
- } else if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(method)) {
- // Implicitly add -identifiernamestring rule for the Java reflection in use.
- identifierNameStrings.add(method);
- // Revisit the current method to implicitly add -keep rule for items with reflective access.
- pendingReflectiveUses.add(currentMethod);
+ List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
+ if (directInterfaces != null) {
+ for (DexType lambdaInstantiatedInterface : directInterfaces) {
+ markLambdaInstantiated(lambdaInstantiatedInterface, currentMethod);
}
- if (!registerMethodWithTargetAndContext(virtualInvokes, method, currentMethod)) {
- return false;
- }
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Register invokeVirtual `%s`.", method);
- }
- workList.enqueueMarkReachableVirtualAction(method, keepReason);
- return true;
- }
-
- @Override
- public boolean registerInvokeDirect(DexMethod method) {
- return registerInvokeDirect(method, KeepReason.invokedFrom(currentHolder, currentMethod));
- }
-
- boolean registerInvokeDirect(DexMethod method, KeepReason keepReason) {
- if (!registerMethodWithTargetAndContext(directInvokes, method, currentMethod)) {
- return false;
- }
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Register invokeDirect `%s`.", method);
- }
- handleInvokeOfDirectTarget(method, keepReason);
- return true;
- }
-
- @Override
- public boolean registerInvokeStatic(DexMethod method) {
- return registerInvokeStatic(method, KeepReason.invokedFrom(currentHolder, currentMethod));
- }
-
- boolean registerInvokeStatic(DexMethod method, KeepReason keepReason) {
- DexItemFactory dexItemFactory = appView.dexItemFactory();
- if (dexItemFactory.classMethods.isReflectiveClassLookup(method)
- || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method)) {
- // Implicitly add -identifiernamestring rule for the Java reflection in use.
- identifierNameStrings.add(method);
- // Revisit the current method to implicitly add -keep rule for items with reflective access.
- pendingReflectiveUses.add(currentMethod);
- }
- // See comment in handleJavaLangEnumValueOf.
- if (method == dexItemFactory.enumMethods.valueOf) {
- pendingReflectiveUses.add(currentMethod);
- }
- // Handling of application services.
- if (dexItemFactory.serviceLoaderMethods.isLoadMethod(method)) {
- pendingReflectiveUses.add(currentMethod);
- }
- if (method == dexItemFactory.proxyMethods.newProxyInstance) {
- pendingReflectiveUses.add(currentMethod);
- }
- if (!registerMethodWithTargetAndContext(staticInvokes, method, currentMethod)) {
- return false;
- }
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Register invokeStatic `%s`.", method);
- }
- handleInvokeOfStaticTarget(method, keepReason);
- return true;
- }
-
- @Override
- public boolean registerInvokeInterface(DexMethod method) {
- return registerInvokeInterface(method, KeepReason.invokedFrom(currentHolder, currentMethod));
- }
-
- boolean registerInvokeInterface(DexMethod method, KeepReason keepReason) {
- if (!registerMethodWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
- return false;
- }
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Register invokeInterface `%s`.", method);
- }
- workList.enqueueMarkReachableInterfaceAction(method, keepReason);
- return true;
- }
-
- @Override
- public boolean registerInvokeSuper(DexMethod method) {
- // We have to revisit super invokes based on the context they are found in. The same
- // method descriptor will hit different targets, depending on the context it is used in.
- DexMethod actualTarget = getInvokeSuperTarget(method, currentMethod);
- if (!registerMethodWithTargetAndContext(superInvokes, method, currentMethod)) {
- return false;
- }
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Register invokeSuper `%s`.", actualTarget);
- }
- workList.enqueueMarkReachableSuperAction(method, currentMethod);
- return true;
- }
-
- @Override
- public boolean registerInstanceFieldWrite(DexField field) {
- if (!registerFieldWrite(field, currentMethod)) {
- return false;
- }
-
- // Must mark the field as targeted even if it does not exist.
- markFieldAsTargeted(field, currentMethod);
-
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField == null) {
- reportMissingField(field);
- return false;
- }
-
- DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
- if (clazz == null) {
- return false;
- }
-
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Register Iput `%s`.", field);
- }
-
- // If it is written outside of the <init>s of its enclosing class, record it.
- boolean isWrittenOutsideEnclosingInstanceInitializers =
- currentMethod.method.holder != encodedField.field.holder
- || !currentMethod.isInstanceInitializer();
- if (isWrittenOutsideEnclosingInstanceInitializers) {
- instanceFieldsWrittenOutsideEnclosingInstanceInitializers.add(encodedField.field);
- }
-
- // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
- // the field as live, if the holder is an interface.
- if (appView.options().enableUnusedInterfaceRemoval) {
- if (encodedField.field != field) {
- markTypeAsLive(clazz, reportClassReferenced(clazz));
- markTypeAsLive(encodedField.field.type, this::reportClassReferenced);
+ } else {
+ if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
+ if (options.reporter != null) {
+ Diagnostic message =
+ new StringDiagnostic(
+ "Unknown bootstrap method " + callSite.bootstrapMethod,
+ appInfo.originFor(currentMethod.method.holder));
+ options.reporter.warning(message);
}
}
+ }
- KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
- workList.enqueueMarkReachableFieldAction(clazz, encodedField, reason);
+ DexProgramClass bootstrapClass =
+ getProgramClassOrNull(callSite.bootstrapMethod.asMethod().holder);
+ if (bootstrapClass != null) {
+ bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
+ }
+
+ LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo);
+ if (descriptor == null) {
+ return;
+ }
+
+ // For call sites representing a lambda, we link the targeted method
+ // or field as if it were referenced from the current method.
+
+ DexMethodHandle implHandle = descriptor.implHandle;
+ assert implHandle != null;
+
+ DexMethod method = implHandle.asMethod();
+ if (descriptor.delegatesToLambdaImplMethod()) {
+ lambdaMethodsTargetedByInvokeDynamic.add(method);
+ }
+
+ if (!methodsTargetedByInvokeDynamic.add(method)) {
+ return;
+ }
+
+ switch (implHandle.type) {
+ case INVOKE_STATIC:
+ traceInvokeStaticFromLambda(method, currentMethod);
+ break;
+ case INVOKE_INTERFACE:
+ traceInvokeInterfaceFromLambda(method, currentMethod);
+ break;
+ case INVOKE_INSTANCE:
+ traceInvokeVirtualFromLambda(method, currentMethod);
+ break;
+ case INVOKE_DIRECT:
+ traceInvokeDirectFromLambda(method, currentMethod);
+ break;
+ case INVOKE_CONSTRUCTOR:
+ traceNewInstanceFromLambda(method.holder, currentMethod);
+ break;
+ default:
+ throw new Unreachable();
+ }
+
+ // In similar way as what transitionMethodsForInstantiatedClass does for existing
+ // classes we need to process classes dynamically created by runtime for lambdas.
+ // We make an assumption that such classes are inherited directly from java.lang.Object
+ // and implement all lambda interfaces.
+
+ if (directInterfaces == null) {
+ return;
+ }
+
+ // The set now contains all virtual methods on the type and its supertype that are reachable.
+ // In a second step, we now look at interfaces. We have to do this in this order due to JVM
+ // semantics for default methods. A default method is only reachable if it is not overridden
+ // in any superclass. Also, it is not defined which default method is chosen if multiple
+ // interfaces define the same default method. Hence, for every interface (direct or indirect),
+ // we have to look at the interface chain and mark default methods as reachable, not taking
+ // the shadowing of other interface chains into account.
+ // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
+ ScopedDexMethodSet seen = new ScopedDexMethodSet();
+ for (DexType iface : directInterfaces) {
+ DexProgramClass ifaceClazz = getProgramClassOrNull(iface);
+ if (ifaceClazz != null) {
+ transitionDefaultMethodsForInstantiatedClass(iface, seen);
+ }
+ }
+ }
+
+ boolean traceCheckCast(DexType type, DexEncodedMethod currentMethod) {
+ return traceConstClassOrCheckCast(type, currentMethod);
+ }
+
+ boolean traceConstClass(DexType type, DexEncodedMethod currentMethod) {
+ // We conservatively group T.class and T[].class to ensure that we do not merge T with S if
+ // potential locks on T[].class and S[].class exists.
+ DexType baseType = type.toBaseType(appView.dexItemFactory());
+ if (baseType.isClassType()) {
+ DexProgramClass baseClass = getProgramClassOrNull(baseType);
+ if (baseClass != null) {
+ constClassReferences.add(baseType);
+ }
+ }
+ return traceConstClassOrCheckCast(type, currentMethod);
+ }
+
+ private boolean traceConstClassOrCheckCast(DexType type, DexEncodedMethod currentMethod) {
+ if (!forceProguardCompatibility) {
+ return traceTypeReference(type, currentMethod);
+ }
+ DexType baseType = type.toBaseType(appView.dexItemFactory());
+ if (baseType.isClassType()) {
+ DexProgramClass baseClass = getProgramClassOrNull(baseType);
+ if (baseClass != null) {
+ // Don't require any constructor, see b/112386012.
+ markClassAsInstantiatedWithCompatRule(
+ baseClass, graphReporter.reportCompatInstantiated(baseClass, currentMethod));
+ }
return true;
}
+ return false;
+ }
- @Override
- public boolean registerInstanceFieldRead(DexField field) {
- if (!registerFieldRead(field, currentMethod)) {
- return false;
- }
-
- // Must mark the field as targeted even if it does not exist.
- markFieldAsTargeted(field, currentMethod);
-
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField == null) {
- reportMissingField(field);
- return false;
- }
-
- DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
- if (clazz == null) {
- return false;
- }
-
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Register Iget `%s`.", field);
- }
-
- // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
- // the field as live, if the holder is an interface.
- if (appView.options().enableUnusedInterfaceRemoval) {
- if (encodedField.field != field) {
- markTypeAsLive(clazz, reportClassReferenced(clazz));
- markTypeAsLive(encodedField.field.type, this::reportClassReferenced);
- }
- }
-
- workList.enqueueMarkReachableFieldAction(
- clazz, encodedField, KeepReason.fieldReferencedIn(currentMethod));
- return true;
- }
-
- @Override
- public boolean registerNewInstance(DexType type) {
- return registerNewInstance(type, currentMethod, KeepReason.instantiatedIn(currentMethod));
- }
-
- public boolean registerNewInstance(
- DexType type, DexEncodedMethod context, KeepReason keepReason) {
+ void traceMethodHandle(
+ DexMethodHandle methodHandle, MethodHandleUse use, DexEncodedMethod currentMethod) {
+ // If a method handle is not an argument to a lambda metafactory it could flow to a
+ // MethodHandle.invokeExact invocation. For that to work, the receiver type cannot have
+ // changed and therefore we cannot perform member rebinding. For these handles, we maintain
+ // the receiver for the method handle. Therefore, we have to make sure that the receiver
+ // stays in the output (and is not class merged). To ensure that we treat the receiver
+ // as instantiated.
+ if (methodHandle.isMethodHandle() && use != MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY) {
+ DexType type = methodHandle.asMethod().holder;
DexProgramClass clazz = getProgramClassOrNull(type);
if (clazz != null) {
- if (clazz.isInterface()) {
- markTypeAsLive(clazz, graphReporter.registerClass(clazz, keepReason));
+ KeepReason reason = KeepReason.methodHandleReferencedIn(currentMethod);
+ if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
+ markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
} else {
- markInstantiated(clazz, context, keepReason);
- }
- }
- return true;
- }
-
- @Override
- public boolean registerStaticFieldRead(DexField field) {
- if (!registerFieldRead(field, currentMethod)) {
- return false;
- }
-
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField == null) {
- // Must mark the field as targeted even if it does not exist.
- markFieldAsTargeted(field, currentMethod);
- reportMissingField(field);
- return false;
- }
-
- if (!isProgramClass(encodedField.field.holder)) {
- // No need to trace into the non-program code.
- return false;
- }
-
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Register Sget `%s`.", field);
- }
-
- if (appView.options().enableGeneratedExtensionRegistryShrinking) {
- // If it is a dead proto extension field, don't trace onwards.
- boolean skipTracing =
- appView.withGeneratedExtensionRegistryShrinker(
- shrinker ->
- shrinker.isDeadProtoExtensionField(encodedField, fieldAccessInfoCollection),
- false);
- if (skipTracing) {
- return false;
- }
- }
-
- if (encodedField.field != field) {
- // Mark the non-rebound field access as targeted. Note that this should only be done if the
- // field is not a dead proto field (in which case we bail-out above).
- markFieldAsTargeted(field, currentMethod);
- }
-
- markStaticFieldAsLive(encodedField, KeepReason.fieldReferencedIn(currentMethod));
- return true;
- }
-
- @Override
- public boolean registerStaticFieldWrite(DexField field) {
- if (!registerFieldWrite(field, currentMethod)) {
- return false;
- }
-
- DexEncodedField encodedField = appInfo.resolveField(field);
- if (encodedField == null) {
- // Must mark the field as targeted even if it does not exist.
- markFieldAsTargeted(field, currentMethod);
- reportMissingField(field);
- return false;
- }
-
- if (!isProgramClass(encodedField.field.holder)) {
- // No need to trace into the non-program code.
- return false;
- }
-
- if (Log.ENABLED) {
- Log.verbose(getClass(), "Register Sput `%s`.", field);
- }
-
- if (appView.options().enableGeneratedExtensionRegistryShrinking) {
- // If it is a dead proto extension field, don't trace onwards.
- boolean skipTracing =
- appView.withGeneratedExtensionRegistryShrinker(
- shrinker ->
- shrinker.isDeadProtoExtensionField(encodedField, fieldAccessInfoCollection),
- false);
- if (skipTracing) {
- return false;
- }
- }
-
- // If it is written outside of the <clinit> of its enclosing class, record it.
- boolean isWrittenOutsideEnclosingStaticInitializer =
- currentMethod.method.holder != encodedField.field.holder
- || !currentMethod.isClassInitializer();
- if (isWrittenOutsideEnclosingStaticInitializer) {
- staticFieldsWrittenOutsideEnclosingStaticInitializer.add(encodedField.field);
- }
-
- if (encodedField.field != field) {
- // Mark the non-rebound field access as targeted. Note that this should only be done if the
- // field is not a dead proto field (in which case we bail-out above).
- markFieldAsTargeted(field, currentMethod);
- }
-
- markStaticFieldAsLive(encodedField, KeepReason.fieldReferencedIn(currentMethod));
- return true;
- }
-
- @Override
- public boolean registerConstClass(DexType type) {
- // We conservatively group T.class and T[].class to ensure that we do not merge T with S if
- // potential locks on T[].class and S[].class exists.
- DexType baseType = type.toBaseType(appView.dexItemFactory());
- if (baseType.isClassType()) {
- DexProgramClass baseClass = getProgramClassOrNull(baseType);
- if (baseClass != null) {
- constClassReferences.add(baseType);
- }
- }
- return registerConstClassOrCheckCast(type);
- }
-
- @Override
- public boolean registerCheckCast(DexType type) {
- return registerConstClassOrCheckCast(type);
- }
-
- @Override
- public boolean registerTypeReference(DexType type) {
- markTypeAsLive(type, this::reportClassReferenced);
- return true;
- }
-
- @Override
- public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
- super.registerMethodHandle(methodHandle, use);
- // If a method handle is not an argument to a lambda metafactory it could flow to a
- // MethodHandle.invokeExact invocation. For that to work, the receiver type cannot have
- // changed and therefore we cannot perform member rebinding. For these handles, we maintain
- // the receiver for the method handle. Therefore, we have to make sure that the receiver
- // stays in the output (and is not class merged). To ensure that we treat the receiver
- // as instantiated.
- if (methodHandle.isMethodHandle() && use != MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY) {
- DexType type = methodHandle.asMethod().holder;
- DexProgramClass clazz = getProgramClassOrNull(type);
- if (clazz != null) {
- KeepReason reason = KeepReason.methodHandleReferencedIn(currentMethod);
- if (clazz.isInterface() && !clazz.accessFlags.isAnnotation()) {
- markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
- } else {
- markInstantiated(clazz, null, reason);
- }
+ markInstantiated(clazz, null, reason);
}
}
}
+ }
- @Override
- public void registerCallSite(DexCallSite callSite) {
- callSites.add(callSite);
- super.registerCallSite(callSite);
+ boolean traceTypeReference(DexType type, DexEncodedMethod currentMethod) {
+ markTypeAsLive(type, classReferencedFromReporter(currentMethod));
+ return true;
+ }
- List<DexType> directInterfaces = LambdaDescriptor.getInterfaces(callSite, appInfo);
- if (directInterfaces != null) {
- for (DexType lambdaInstantiatedInterface : directInterfaces) {
- markLambdaInstantiated(lambdaInstantiatedInterface, currentMethod);
- }
- } else {
- if (!appInfo.isStringConcat(callSite.bootstrapMethod)) {
- if (options.reporter != null) {
- Diagnostic message =
- new StringDiagnostic(
- "Unknown bootstrap method " + callSite.bootstrapMethod,
- appInfo.originFor(currentMethod.method.holder));
- options.reporter.warning(message);
- }
- }
- }
+ boolean traceInvokeDirect(
+ DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+ return traceInvokeDirect(
+ invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
+ }
- DexProgramClass bootstrapClass =
- getProgramClassOrNull(callSite.bootstrapMethod.asMethod().holder);
- if (bootstrapClass != null) {
- bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
- }
+ boolean traceInvokeDirectFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
+ return traceInvokeDirect(
+ invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ }
- LambdaDescriptor descriptor = LambdaDescriptor.tryInfer(callSite, appInfo);
- if (descriptor == null) {
- return;
- }
-
- // For call sites representing a lambda, we link the targeted method
- // or field as if it were referenced from the current method.
-
- DexMethodHandle implHandle = descriptor.implHandle;
- assert implHandle != null;
-
- DexMethod method = implHandle.asMethod();
- if (descriptor.delegatesToLambdaImplMethod()) {
- lambdaMethodsTargetedByInvokeDynamic.add(method);
- }
-
- if (!methodsTargetedByInvokeDynamic.add(method)) {
- return;
- }
-
- switch (implHandle.type) {
- case INVOKE_STATIC:
- registerInvokeStatic(method, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
- break;
- case INVOKE_INTERFACE:
- registerInvokeInterface(method, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
- break;
- case INVOKE_INSTANCE:
- registerInvokeVirtual(method, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
- break;
- case INVOKE_DIRECT:
- registerInvokeDirect(method, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
- break;
- case INVOKE_CONSTRUCTOR:
- registerNewInstance(
- method.holder, null, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
- break;
- default:
- throw new Unreachable();
- }
-
- // In similar way as what transitionMethodsForInstantiatedClass does for existing
- // classes we need to process classes dynamically created by runtime for lambdas.
- // We make an assumption that such classes are inherited directly from java.lang.Object
- // and implement all lambda interfaces.
-
- if (directInterfaces == null) {
- return;
- }
-
- // The set now contains all virtual methods on the type and its supertype that are reachable.
- // In a second step, we now look at interfaces. We have to do this in this order due to JVM
- // semantics for default methods. A default method is only reachable if it is not overridden
- // in any superclass. Also, it is not defined which default method is chosen if multiple
- // interfaces define the same default method. Hence, for every interface (direct or indirect),
- // we have to look at the interface chain and mark default methods as reachable, not taking
- // the shadowing of other interface chains into account.
- // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
- ScopedDexMethodSet seen = new ScopedDexMethodSet();
- for (DexType iface : directInterfaces) {
- DexProgramClass ifaceClazz = getProgramClassOrNull(iface);
- if (ifaceClazz != null) {
- transitionDefaultMethodsForInstantiatedClass(iface, seen);
- }
- }
- }
-
- private boolean registerConstClassOrCheckCast(DexType type) {
- if (!forceProguardCompatibility) {
- return registerTypeReference(type);
- }
- DexType baseType = type.toBaseType(appView.dexItemFactory());
- if (baseType.isClassType()) {
- DexProgramClass baseClass = getProgramClassOrNull(baseType);
- if (baseClass != null) {
- // Don't require any constructor, see b/112386012.
- markClassAsInstantiatedWithCompatRule(
- baseClass, graphReporter.reportCompatInstantiated(baseClass, currentMethod));
- }
- return true;
- }
+ private boolean traceInvokeDirect(
+ DexMethod invokedMethod, DexEncodedMethod currentMethod, KeepReason reason) {
+ if (!registerMethodWithTargetAndContext(directInvokes, invokedMethod, currentMethod)) {
return false;
}
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Register invokeDirect `%s`.", invokedMethod);
+ }
+ handleInvokeOfDirectTarget(invokedMethod, reason);
+ return true;
+ }
+
+ boolean traceInvokeInterface(
+ DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+ return traceInvokeInterface(
+ invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
+ }
+
+ boolean traceInvokeInterfaceFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
+ return traceInvokeInterface(
+ invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ }
+
+ private boolean traceInvokeInterface(
+ DexMethod method, DexEncodedMethod currentMethod, KeepReason keepReason) {
+ if (!registerMethodWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
+ return false;
+ }
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Register invokeInterface `%s`.", method);
+ }
+ workList.enqueueMarkReachableInterfaceAction(method, keepReason);
+ return true;
+ }
+
+ boolean traceInvokeStatic(
+ DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+ return traceInvokeStatic(
+ invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
+ }
+
+ boolean traceInvokeStaticFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
+ return traceInvokeStatic(
+ invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ }
+
+ private boolean traceInvokeStatic(
+ DexMethod invokedMethod, DexEncodedMethod currentMethod, KeepReason reason) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ if (dexItemFactory.classMethods.isReflectiveClassLookup(invokedMethod)
+ || dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod)) {
+ // Implicitly add -identifiernamestring rule for the Java reflection in use.
+ identifierNameStrings.add(invokedMethod);
+ // Revisit the current method to implicitly add -keep rule for items with reflective access.
+ pendingReflectiveUses.add(currentMethod);
+ }
+ // See comment in handleJavaLangEnumValueOf.
+ if (invokedMethod == dexItemFactory.enumMethods.valueOf) {
+ pendingReflectiveUses.add(currentMethod);
+ }
+ // Handling of application services.
+ if (dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) {
+ pendingReflectiveUses.add(currentMethod);
+ }
+ if (invokedMethod == dexItemFactory.proxyMethods.newProxyInstance) {
+ pendingReflectiveUses.add(currentMethod);
+ }
+ if (!registerMethodWithTargetAndContext(staticInvokes, invokedMethod, currentMethod)) {
+ return false;
+ }
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Register invokeStatic `%s`.", invokedMethod);
+ }
+ handleInvokeOfStaticTarget(invokedMethod, reason);
+ return true;
+ }
+
+ boolean traceInvokeSuper(
+ DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+ // We have to revisit super invokes based on the context they are found in. The same
+ // method descriptor will hit different targets, depending on the context it is used in.
+ DexMethod actualTarget = getInvokeSuperTarget(invokedMethod, currentMethod);
+ if (!registerMethodWithTargetAndContext(superInvokes, invokedMethod, currentMethod)) {
+ return false;
+ }
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Register invokeSuper `%s`.", actualTarget);
+ }
+ workList.enqueueMarkReachableSuperAction(invokedMethod, currentMethod);
+ return true;
+ }
+
+ boolean traceInvokeVirtual(
+ DexMethod invokedMethod, DexProgramClass currentHolder, DexEncodedMethod currentMethod) {
+ return traceInvokeVirtual(
+ invokedMethod, currentMethod, KeepReason.invokedFrom(currentHolder, currentMethod));
+ }
+
+ boolean traceInvokeVirtualFromLambda(DexMethod invokedMethod, DexEncodedMethod currentMethod) {
+ return traceInvokeVirtual(
+ invokedMethod, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ }
+
+ private boolean traceInvokeVirtual(
+ DexMethod invokedMethod, DexEncodedMethod currentMethod, KeepReason reason) {
+ if (invokedMethod == appView.dexItemFactory().classMethods.newInstance
+ || invokedMethod == appView.dexItemFactory().constructorMethods.newInstance) {
+ pendingReflectiveUses.add(currentMethod);
+ } else if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(invokedMethod)) {
+ // Implicitly add -identifiernamestring rule for the Java reflection in use.
+ identifierNameStrings.add(invokedMethod);
+ // Revisit the current method to implicitly add -keep rule for items with reflective access.
+ pendingReflectiveUses.add(currentMethod);
+ }
+ if (!registerMethodWithTargetAndContext(virtualInvokes, invokedMethod, currentMethod)) {
+ return false;
+ }
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Register invokeVirtual `%s`.", invokedMethod);
+ }
+ workList.enqueueMarkReachableVirtualAction(invokedMethod, reason);
+ return true;
+ }
+
+ boolean traceNewInstance(DexType type, DexEncodedMethod currentMethod) {
+ return traceNewInstance(type, currentMethod, KeepReason.instantiatedIn(currentMethod));
+ }
+
+ boolean traceNewInstanceFromLambda(DexType type, DexEncodedMethod currentMethod) {
+ return traceNewInstance(
+ type, currentMethod, KeepReason.invokedFromLambdaCreatedIn(currentMethod));
+ }
+
+ private boolean traceNewInstance(
+ DexType type, DexEncodedMethod currentMethod, KeepReason keepReason) {
+ DexProgramClass clazz = getProgramClassOrNull(type);
+ if (clazz != null) {
+ if (clazz.isInterface()) {
+ markTypeAsLive(clazz, graphReporter.registerClass(clazz, keepReason));
+ } else {
+ markInstantiated(clazz, currentMethod, keepReason);
+ }
+ }
+ return true;
+ }
+
+ boolean traceInstanceFieldRead(DexField field, DexEncodedMethod currentMethod) {
+ if (!registerFieldRead(field, currentMethod)) {
+ return false;
+ }
+
+ // Must mark the field as targeted even if it does not exist.
+ markFieldAsTargeted(field, currentMethod);
+
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField == null) {
+ reportMissingField(field);
+ return false;
+ }
+
+ DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
+ if (clazz == null) {
+ return false;
+ }
+
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Register Iget `%s`.", field);
+ }
+
+ // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
+ // the field as live, if the holder is an interface.
+ if (appView.options().enableUnusedInterfaceRemoval) {
+ if (encodedField.field != field) {
+ markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, currentMethod));
+ markTypeAsLive(encodedField.field.type, classReferencedFromReporter(currentMethod));
+ }
+ }
+
+ workList.enqueueMarkReachableFieldAction(
+ clazz, encodedField, KeepReason.fieldReferencedIn(currentMethod));
+ return true;
+ }
+
+ boolean traceInstanceFieldWrite(DexField field, DexEncodedMethod currentMethod) {
+ if (!registerFieldWrite(field, currentMethod)) {
+ return false;
+ }
+
+ // Must mark the field as targeted even if it does not exist.
+ markFieldAsTargeted(field, currentMethod);
+
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField == null) {
+ reportMissingField(field);
+ return false;
+ }
+
+ DexProgramClass clazz = getProgramClassOrNull(encodedField.field.holder);
+ if (clazz == null) {
+ return false;
+ }
+
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Register Iput `%s`.", field);
+ }
+
+ // If it is written outside of the <init>s of its enclosing class, record it.
+ boolean isWrittenOutsideEnclosingInstanceInitializers =
+ currentMethod.method.holder != encodedField.field.holder
+ || !currentMethod.isInstanceInitializer();
+ if (isWrittenOutsideEnclosingInstanceInitializers) {
+ instanceFieldsWrittenOutsideEnclosingInstanceInitializers.add(encodedField.field);
+ }
+
+ // If unused interface removal is enabled, then we won't necessarily mark the actual holder of
+ // the field as live, if the holder is an interface.
+ if (appView.options().enableUnusedInterfaceRemoval) {
+ if (encodedField.field != field) {
+ markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, currentMethod));
+ markTypeAsLive(encodedField.field.type, classReferencedFromReporter(currentMethod));
+ }
+ }
+
+ KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
+ workList.enqueueMarkReachableFieldAction(clazz, encodedField, reason);
+ return true;
+ }
+
+ boolean traceStaticFieldRead(DexField field, DexEncodedMethod currentMethod) {
+ if (!registerFieldRead(field, currentMethod)) {
+ return false;
+ }
+
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField == null) {
+ // Must mark the field as targeted even if it does not exist.
+ markFieldAsTargeted(field, currentMethod);
+ reportMissingField(field);
+ return false;
+ }
+
+ if (!isProgramClass(encodedField.field.holder)) {
+ // No need to trace into the non-program code.
+ return false;
+ }
+
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Register Sget `%s`.", field);
+ }
+
+ if (appView.options().enableGeneratedExtensionRegistryShrinking) {
+ // If it is a dead proto extension field, don't trace onwards.
+ boolean skipTracing =
+ appView.withGeneratedExtensionRegistryShrinker(
+ shrinker ->
+ shrinker.isDeadProtoExtensionField(encodedField, fieldAccessInfoCollection),
+ false);
+ if (skipTracing) {
+ return false;
+ }
+ }
+
+ if (encodedField.field != field) {
+ // Mark the non-rebound field access as targeted. Note that this should only be done if the
+ // field is not a dead proto field (in which case we bail-out above).
+ markFieldAsTargeted(field, currentMethod);
+ }
+
+ markStaticFieldAsLive(encodedField, KeepReason.fieldReferencedIn(currentMethod));
+ return true;
+ }
+
+ boolean traceStaticFieldWrite(DexField field, DexEncodedMethod currentMethod) {
+ if (!registerFieldWrite(field, currentMethod)) {
+ return false;
+ }
+
+ DexEncodedField encodedField = appInfo.resolveField(field);
+ if (encodedField == null) {
+ // Must mark the field as targeted even if it does not exist.
+ markFieldAsTargeted(field, currentMethod);
+ reportMissingField(field);
+ return false;
+ }
+
+ if (!isProgramClass(encodedField.field.holder)) {
+ // No need to trace into the non-program code.
+ return false;
+ }
+
+ if (Log.ENABLED) {
+ Log.verbose(getClass(), "Register Sput `%s`.", field);
+ }
+
+ if (appView.options().enableGeneratedExtensionRegistryShrinking) {
+ // If it is a dead proto extension field, don't trace onwards.
+ boolean skipTracing =
+ appView.withGeneratedExtensionRegistryShrinker(
+ shrinker ->
+ shrinker.isDeadProtoExtensionField(encodedField, fieldAccessInfoCollection),
+ false);
+ if (skipTracing) {
+ return false;
+ }
+ }
+
+ // If it is written outside of the <clinit> of its enclosing class, record it.
+ boolean isWrittenOutsideEnclosingStaticInitializer =
+ currentMethod.method.holder != encodedField.field.holder
+ || !currentMethod.isClassInitializer();
+ if (isWrittenOutsideEnclosingStaticInitializer) {
+ staticFieldsWrittenOutsideEnclosingStaticInitializer.add(encodedField.field);
+ }
+
+ if (encodedField.field != field) {
+ // Mark the non-rebound field access as targeted. Note that this should only be done if the
+ // field is not a dead proto field (in which case we bail-out above).
+ markFieldAsTargeted(field, currentMethod);
+ }
+
+ markStaticFieldAsLive(encodedField, KeepReason.fieldReferencedIn(currentMethod));
+ return true;
+ }
+
+ private Function<DexProgramClass, KeepReasonWitness> classReferencedFromReporter(
+ DexEncodedMethod currentMethod) {
+ return clazz -> graphReporter.reportClassReferencedFrom(clazz, currentMethod);
}
private void transitionReachableVirtualMethods(DexProgramClass clazz, ScopedDexMethodSet seen) {
@@ -2472,7 +2482,7 @@
processAnnotations(method, method.annotations.annotations);
method.parameterAnnotationsList.forEachAnnotation(
annotation -> processAnnotation(method, annotation));
- method.registerCodeReferences(new UseRegistry(options.itemFactory, clazz, method));
+ method.registerCodeReferences(new EnqueuerUseRegistry(appView, clazz, method, this));
// Add all dependent members to the workqueue.
enqueueRootItems(rootSet.getDependentItems(method));
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistry.java
new file mode 100644
index 0000000..acd10ed
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistry.java
@@ -0,0 +1,111 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+
+class EnqueuerUseRegistry extends UseRegistry {
+
+ private final DexProgramClass currentHolder;
+ private final DexEncodedMethod currentMethod;
+ private final Enqueuer enqueuer;
+
+ EnqueuerUseRegistry(
+ AppView<?> appView,
+ DexProgramClass currentHolder,
+ DexEncodedMethod currentMethod,
+ Enqueuer enqueuer) {
+ super(appView.dexItemFactory());
+ assert currentHolder.type == currentMethod.method.holder;
+ this.currentHolder = currentHolder;
+ this.currentMethod = currentMethod;
+ this.enqueuer = enqueuer;
+ }
+
+ @Override
+ public boolean registerInvokeVirtual(DexMethod invokedMethod) {
+ return enqueuer.traceInvokeVirtual(invokedMethod, currentHolder, currentMethod);
+ }
+
+ @Override
+ public boolean registerInvokeDirect(DexMethod invokedMethod) {
+ return enqueuer.traceInvokeDirect(invokedMethod, currentHolder, currentMethod);
+ }
+
+ @Override
+ public boolean registerInvokeStatic(DexMethod invokedMethod) {
+ return enqueuer.traceInvokeStatic(invokedMethod, currentHolder, currentMethod);
+ }
+
+ @Override
+ public boolean registerInvokeInterface(DexMethod invokedMethod) {
+ return enqueuer.traceInvokeInterface(invokedMethod, currentHolder, currentMethod);
+ }
+
+ @Override
+ public boolean registerInvokeSuper(DexMethod invokedMethod) {
+ return enqueuer.traceInvokeSuper(invokedMethod, currentHolder, currentMethod);
+ }
+
+ @Override
+ public boolean registerInstanceFieldWrite(DexField field) {
+ return enqueuer.traceInstanceFieldWrite(field, currentMethod);
+ }
+
+ @Override
+ public boolean registerInstanceFieldRead(DexField field) {
+ return enqueuer.traceInstanceFieldRead(field, currentMethod);
+ }
+
+ @Override
+ public boolean registerNewInstance(DexType type) {
+ return enqueuer.traceNewInstance(type, currentMethod);
+ }
+
+ @Override
+ public boolean registerStaticFieldRead(DexField field) {
+ return enqueuer.traceStaticFieldRead(field, currentMethod);
+ }
+
+ @Override
+ public boolean registerStaticFieldWrite(DexField field) {
+ return enqueuer.traceStaticFieldWrite(field, currentMethod);
+ }
+
+ @Override
+ public boolean registerConstClass(DexType type) {
+ return enqueuer.traceConstClass(type, currentMethod);
+ }
+
+ @Override
+ public boolean registerCheckCast(DexType type) {
+ return enqueuer.traceCheckCast(type, currentMethod);
+ }
+
+ @Override
+ public boolean registerTypeReference(DexType type) {
+ return enqueuer.traceTypeReference(type, currentMethod);
+ }
+
+ @Override
+ public void registerMethodHandle(DexMethodHandle methodHandle, MethodHandleUse use) {
+ super.registerMethodHandle(methodHandle, use);
+ enqueuer.traceMethodHandle(methodHandle, use, currentMethod);
+ }
+
+ @Override
+ public void registerCallSite(DexCallSite callSite) {
+ super.registerCallSite(callSite);
+ enqueuer.traceCallSite(callSite, currentMethod);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index cccf0f4..b7e9923 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
@@ -32,16 +33,20 @@
public class TreePruner {
- private final DexApplication application;
private final AppView<AppInfoWithLiveness> appView;
+ private final TreePrunerConfiguration configuration;
private final UsagePrinter usagePrinter;
private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
private final Set<DexMethod> methodsToKeepForConfigurationDebugging = Sets.newIdentityHashSet();
- public TreePruner(DexApplication application, AppView<AppInfoWithLiveness> appView) {
+ public TreePruner(AppView<AppInfoWithLiveness> appView) {
+ this(appView, DefaultTreePrunerConfiguration.getInstance());
+ }
+
+ public TreePruner(AppView<AppInfoWithLiveness> appView, TreePrunerConfiguration configuration) {
InternalOptions options = appView.options();
- this.application = application;
this.appView = appView;
+ this.configuration = configuration;
this.usagePrinter =
options.hasUsageInformationConsumer()
? new UsagePrinter(
@@ -51,13 +56,14 @@
: UsagePrinter.DONT_PRINT;
}
- public DexApplication run() {
- application.timing.begin("Pruning application...");
+ public DexApplication run(DexApplication application) {
+ Timing timing = application.timing;
+ timing.begin("Pruning application...");
DexApplication result;
try {
result = removeUnused(application).build();
} finally {
- application.timing.end();
+ timing.end();
}
return result;
}
@@ -309,7 +315,7 @@
private DexEncodedField[] reachableFields(List<DexEncodedField> fields) {
AppInfoWithLiveness appInfo = appView.appInfo();
Predicate<DexEncodedField> isReachableOrReferencedField =
- field -> appInfo.isFieldRead(field) || appInfo.isFieldWritten(field);
+ field -> configuration.isReachableOrReferencedField(appInfo, field);
int firstUnreachable = firstUnreachableIndex(fields, isReachableOrReferencedField);
// Return the original array if all fields are used.
if (firstUnreachable == -1) {
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePrunerConfiguration.java b/src/main/java/com/android/tools/r8/shaking/TreePrunerConfiguration.java
new file mode 100644
index 0000000..41edc42
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/TreePrunerConfiguration.java
@@ -0,0 +1,12 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexEncodedField;
+
+public interface TreePrunerConfiguration {
+
+ boolean isReachableOrReferencedField(AppInfoWithLiveness appInfo, DexEncodedField field);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 14f705e..c4a37c2 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -31,6 +31,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.shaking.FilteredClassPath;
@@ -38,9 +39,12 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
@@ -52,12 +56,13 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.Consumer;
import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import org.objectweb.asm.ClassVisitor;
@@ -68,6 +73,12 @@
*/
public class AndroidApp {
+ private static final String dumpVersionFileName = "r8-version";
+ private static final String dumpProgramFileName = "program.jar";
+ private static final String dumpClasspathFileName = "classpath.jar";
+ private static final String dumpLibraryFileName = "library.jar";
+ private static final String dumpConfigFileName = "proguard.config";
+
private final ImmutableList<ProgramResourceProvider> programResourceProviders;
private final ImmutableMap<Resource, String> programResourcesMainDescriptor;
private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
@@ -395,50 +406,81 @@
return programResourcesMainDescriptor.get(resource);
}
- public AndroidApp dump(Path output, ProguardConfiguration configuration, Reporter reporter) {
+ public void dump(Path output, ProguardConfiguration configuration, Reporter reporter) {
int nextDexIndex = 0;
- Builder builder = AndroidApp.builder(reporter);
OpenOption[] openOptions =
new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(output, openOptions))) {
- writeToZipStream(out, "r8-version", Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
+ writeToZipStream(
+ out, dumpVersionFileName, Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
if (configuration != null) {
String proguardConfig = configuration.getParsedConfiguration();
- writeToZipStream(out, "proguard.config", proguardConfig.getBytes(), ZipEntry.DEFLATED);
+ writeToZipStream(out, dumpConfigFileName, proguardConfig.getBytes(), ZipEntry.DEFLATED);
}
- nextDexIndex = dumpProgramResources("program.jar", nextDexIndex, out, builder);
- nextDexIndex =
- dumpClassFileResources(
- "classpath.jar", nextDexIndex, out, builder, classpathResourceProviders);
- nextDexIndex =
- dumpClassFileResources(
- "library.jar", nextDexIndex, out, builder, libraryResourceProviders);
+ nextDexIndex = dumpProgramResources(dumpProgramFileName, nextDexIndex, out);
+ nextDexIndex = dumpClasspathResources(nextDexIndex, out);
+ nextDexIndex = dumpLibraryResources(nextDexIndex, out);
} catch (IOException | ResourceException e) {
reporter.fatalError(new StringDiagnostic("Failed to dump inputs"), e);
}
- return builder.build();
}
- private int dumpProgramResources(
- String archiveName, int nextDexIndex, ZipOutputStream out, Builder builder)
+ private int dumpLibraryResources(int nextDexIndex, ZipOutputStream out)
+ throws IOException, ResourceException {
+ nextDexIndex =
+ dumpClassFileResources(dumpLibraryFileName, nextDexIndex, out, libraryResourceProviders);
+ return nextDexIndex;
+ }
+
+ private int dumpClasspathResources(int nextDexIndex, ZipOutputStream out)
+ throws IOException, ResourceException {
+ nextDexIndex =
+ dumpClassFileResources(
+ dumpClasspathFileName, nextDexIndex, out, classpathResourceProviders);
+ return nextDexIndex;
+ }
+
+ private static ClassFileResourceProvider createClassFileResourceProvider(
+ Map<String, ProgramResource> classPathResources) {
+ return new ClassFileResourceProvider() {
+ @Override
+ public Set<String> getClassDescriptors() {
+ return classPathResources.keySet();
+ }
+
+ @Override
+ public ProgramResource getProgramResource(String descriptor) {
+ return classPathResources.get(descriptor);
+ }
+ };
+ }
+
+ private Consumer<ProgramResource> createClassFileResourceConsumer(
+ Map<String, ProgramResource> classPathResources) {
+ return programResource -> {
+ assert programResource.getClassDescriptors().size() == 1;
+ String descriptor = programResource.getClassDescriptors().iterator().next();
+ classPathResources.put(descriptor, programResource);
+ };
+ }
+
+ private int dumpProgramResources(String archiveName, int nextDexIndex, ZipOutputStream out)
throws IOException, ResourceException {
try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
- Set<String> seen = new HashSet<>();
+ Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
Set<DataEntryResource> dataEntries = getDataEntryResourcesForTesting();
for (DataEntryResource dataResource : dataEntries) {
- builder.addDataResources(dataResource);
String entryName = dataResource.getName();
try (InputStream dataStream = dataResource.getByteStream()) {
byte[] bytes = ByteStreams.toByteArray(dataStream);
- writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+ writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
}
}
for (ProgramResourceProvider provider : programResourceProviders) {
for (ProgramResource programResource : provider.getProgramResources()) {
nextDexIndex =
- dumpProgramResource(
- builder, seen, nextDexIndex, archiveOutputStream, programResource);
+ dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
}
}
}
@@ -451,19 +493,17 @@
String archiveName,
int nextDexIndex,
ZipOutputStream out,
- Builder builder,
ImmutableList<ClassFileResourceProvider> classpathResourceProviders)
throws IOException, ResourceException {
try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
- Set<String> seen = new HashSet<>();
+ Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
for (ClassFileResourceProvider provider : classpathResourceProviders) {
for (String descriptor : provider.getClassDescriptors()) {
ProgramResource programResource = provider.getProgramResource(descriptor);
int oldDexIndex = nextDexIndex;
nextDexIndex =
- dumpProgramResource(
- builder, seen, nextDexIndex, archiveOutputStream, programResource);
+ dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
assert nextDexIndex == oldDexIndex;
}
}
@@ -474,33 +514,23 @@
}
private static int dumpProgramResource(
- Builder builder,
- Set<String> seen,
+ Object2IntMap<String> seen,
int nextDexIndex,
ZipOutputStream archiveOutputStream,
ProgramResource programResource)
throws ResourceException, IOException {
byte[] bytes = ByteStreams.toByteArray(programResource.getByteStream());
- Set<String> classDescriptors = programResource.getClassDescriptors();
- if (classDescriptors.size() != 1 && programResource.getKind() == Kind.CF) {
- classDescriptors = Collections.singleton(extractClassDescriptor(bytes));
- }
- if (programResource instanceof OneShotByteResource) {
- builder.addProgramResources(
- OneShotByteResource.create(
- programResource.getKind(), programResource.getOrigin(), bytes, classDescriptors));
-
- } else {
- builder.addProgramResources(programResource);
- }
String entryName;
if (programResource.getKind() == Kind.CF) {
+ Set<String> classDescriptors = programResource.getClassDescriptors();
String classDescriptor =
- classDescriptors.size() == 1
- ? classDescriptors.iterator().next()
- : extractClassDescriptor(bytes);
+ (classDescriptors == null || classDescriptors.size() != 1)
+ ? extractClassDescriptor(bytes)
+ : classDescriptors.iterator().next();
String classFileName = DescriptorUtils.getClassFileName(classDescriptor);
- entryName = seen.add(classDescriptor) ? classFileName : (classFileName + ".dup");
+ int dupCount = seen.getOrDefault(classDescriptor, 0);
+ seen.put(classDescriptor, dupCount + 1);
+ entryName = dupCount == 0 ? classFileName : (classFileName + "." + dupCount + ".dup");
} else {
assert programResource.getKind() == Kind.DEX;
entryName = "classes" + nextDexIndex++ + ".dex";
@@ -585,6 +615,118 @@
return reporter;
}
+ public Builder addDump(Path dumpFile) throws IOException {
+ System.out.println("Reading dump from file: " + dumpFile);
+ Origin origin = new PathOrigin(dumpFile);
+ ZipUtils.iter(
+ dumpFile.toString(),
+ (entry, input) -> {
+ String name = entry.getName();
+ if (name.equals(dumpVersionFileName)) {
+ String content = new String(ByteStreams.toByteArray(input), StandardCharsets.UTF_8);
+ System.out.println("Dump produced by R8 version: " + content);
+ } else if (name.equals(dumpProgramFileName)) {
+ readProgramDump(origin, input);
+ } else if (name.equals(dumpClasspathFileName)) {
+ readClassFileDump(origin, input, this::addClasspathResourceProvider, "classpath");
+ } else if (name.equals(dumpLibraryFileName)) {
+ readClassFileDump(origin, input, this::addLibraryResourceProvider, "library");
+ } else {
+ System.out.println("WARNING: Unexpected dump file entry: " + entry.getName());
+ }
+ });
+ return this;
+ }
+
+ private void readClassFileDump(
+ Origin origin,
+ InputStream input,
+ Consumer<ClassFileResourceProvider> addProvider,
+ String inputType)
+ throws IOException {
+ Map<String, ProgramResource> resources = new HashMap<>();
+ try (ZipInputStream stream = new ZipInputStream(input)) {
+ ZipEntry entry;
+ while (null != (entry = stream.getNextEntry())) {
+ String name = entry.getName();
+ if (ZipUtils.isClassFile(name)) {
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+ String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+ ProgramResource resource =
+ OneShotByteResource.create(
+ Kind.CF,
+ entryOrigin,
+ ByteStreams.toByteArray(stream),
+ Collections.singleton(descriptor));
+ resources.put(descriptor, resource);
+ } else if (name.endsWith(".dup")) {
+ System.out.println("WARNING: Duplicate " + inputType + " resource: " + name);
+ } else {
+ System.out.println("WARNING: Unexpected " + inputType + " resource: " + name);
+ }
+ }
+ }
+ if (!resources.isEmpty()) {
+ addProvider.accept(createClassFileResourceProvider(resources));
+ }
+ }
+
+ private void readProgramDump(Origin origin, InputStream input) throws IOException {
+ List<ProgramResource> programResources = new ArrayList<>();
+ List<DataEntryResource> dataResources = new ArrayList<>();
+ try (ZipInputStream stream = new ZipInputStream(input)) {
+ ZipEntry entry;
+ while (null != (entry = stream.getNextEntry())) {
+ String name = entry.getName();
+ if (ZipUtils.isClassFile(name)) {
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+ String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+ ProgramResource resource =
+ OneShotByteResource.create(
+ Kind.CF,
+ entryOrigin,
+ ByteStreams.toByteArray(stream),
+ Collections.singleton(descriptor));
+ programResources.add(resource);
+ } else if (ZipUtils.isDexFile(name)) {
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+ ProgramResource resource =
+ OneShotByteResource.create(
+ Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null);
+ programResources.add(resource);
+ } else if (name.endsWith(".dup")) {
+ System.out.println("WARNING: Duplicate program resource: " + name);
+ } else {
+ dataResources.add(
+ DataEntryResource.fromBytes(ByteStreams.toByteArray(stream), name, origin));
+ }
+ }
+ }
+ if (!programResources.isEmpty() || !dataResources.isEmpty()) {
+ addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return programResources;
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return dataResources.isEmpty()
+ ? null
+ : new DataResourceProvider() {
+ @Override
+ public void accept(Visitor visitor) throws ResourceException {
+ for (DataEntryResource dataResource : dataResources) {
+ visitor.visit(dataResource);
+ }
+ }
+ };
+ }
+ });
+ }
+ }
+
/** Add program file resources. */
public Builder addProgramFiles(Path... files) {
return addProgramFiles(Arrays.asList(files));
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 828e8d2..55b5150 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -177,6 +177,8 @@
public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null;
public String dumpInputToFile = System.getProperty("com.android.tools.r8.dumpinputtofile");
+ public String dumpInputToDirectory =
+ System.getProperty("com.android.tools.r8.dumpinputtodirectory");
// Flag to toggle if DEX code objects should pass-through without IR processing.
public boolean passthroughDexCode = false;
@@ -218,11 +220,6 @@
public boolean encodeChecksums = false;
public BiPredicate<String, Long> dexClassChecksumFilter = (name, checksum) -> true;
- // This defines the max depth threshold for the cycle eliminator. If the length of a call chain
- // exceeds the threshold, we treat it as if we have found a cycle. This ensures that we won't run
- // into stack overflows when the call graph contains large call chains. This should have a
- // negligible impact on code size as long as the threshold is large enough.
- public int callGraphCycleEliminatorMaxDepthThreshold = 256;
public int callGraphLikelySpuriousCallEdgeThreshold = 50;
// TODO(b/141719453): The inlining limit at least should be consistent with normal inlining.
@@ -985,6 +982,10 @@
public boolean enableForceNestBasedAccessDesugaringForTest = false;
public boolean verifyKeptGraphInfo = false;
+ // Force each call of application read to dump its inputs to a file, which is subsequently
+ // deleted. Useful to check that our dump functionality does not cause compilation failure.
+ public boolean dumpAll = false;
+
public boolean desugarLambdasThroughLensCodeRewriter() {
return enableStatefulLambdaCreateInstanceMethod;
}
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
new file mode 100644
index 0000000..04c4bf2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -0,0 +1,183 @@
+// 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Reporter;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AndroidAppDumpsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public AndroidAppDumpsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ Reporter reporter = new Reporter();
+
+ String dataResourceName = "my-resource.bin";
+ byte[] dataResourceData = new byte[] {1, 2, 3};
+
+ Path dexForB =
+ testForD8().addProgramClasses(B.class).setMinApi(AndroidApiLevel.B).compile().writeToZip();
+
+ AndroidApp appIn =
+ AndroidApp.builder(reporter)
+ .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
+ .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
+ .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
+ .addProgramFile(dexForB)
+ .addClasspathResourceProvider(provider(B.class))
+ .addClasspathResourceProvider(provider(B.class))
+ .addClasspathResourceProvider(provider(B.class))
+ .addLibraryResourceProvider(provider(C.class))
+ .addLibraryResourceProvider(provider(C.class))
+ .addLibraryResourceProvider(provider(C.class))
+ .addProgramResourceProvider(
+ createDataResourceProvider(
+ dataResourceName, dataResourceData, origin(dataResourceName)))
+ .build();
+
+ Path dumpFile = temp.newFolder().toPath().resolve("dump.zip");
+ appIn.dump(dumpFile, null, reporter);
+
+ AndroidApp appOut = AndroidApp.builder(reporter).addDump(dumpFile).build();
+ assertEquals(1, appOut.getClassProgramResourcesForTesting().size());
+ assertEquals(
+ DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()),
+ appOut.getClassProgramResourcesForTesting().get(0).getClassDescriptors().iterator().next());
+
+ assertEquals(1, appOut.getDexProgramResourcesForTesting().size());
+
+ assertEquals(1, appOut.getClasspathResourceProviders().size());
+ assertEquals(
+ DescriptorUtils.javaTypeToDescriptor(B.class.getTypeName()),
+ appOut.getClasspathResourceProviders().get(0).getClassDescriptors().iterator().next());
+
+ assertEquals(1, appOut.getLibraryResourceProviders().size());
+ assertEquals(
+ DescriptorUtils.javaTypeToDescriptor(C.class.getTypeName()),
+ appOut.getLibraryResourceProviders().get(0).getClassDescriptors().iterator().next());
+
+ Box<Boolean> foundData = new Box<>(false);
+ for (ProgramResourceProvider provider : appOut.getProgramResourceProviders()) {
+ DataResourceProvider dataProvider = provider.getDataResourceProvider();
+ if (dataProvider != null) {
+ dataProvider.accept(
+ new Visitor() {
+ @Override
+ public void visit(DataDirectoryResource directory) {}
+
+ @Override
+ public void visit(DataEntryResource file) {
+ if (file.getName().equals(dataResourceName)) {
+ foundData.set(true);
+ }
+ }
+ });
+ }
+ }
+ assertTrue(foundData.get());
+ }
+
+ private ProgramResourceProvider createDataResourceProvider(
+ String name, byte[] content, Origin origin) {
+ return new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return new DataResourceProvider() {
+ @Override
+ public void accept(Visitor visitor) throws ResourceException {
+ visitor.visit(
+ new DataEntryResource() {
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ return new ByteArrayInputStream(content);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+ });
+ }
+ };
+ }
+ };
+ }
+
+ private Origin origin(String name) {
+ return new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return name;
+ }
+ };
+ }
+
+ private ClassFileResourceProvider provider(Class<?> clazz) {
+ return new ClassFileResourceProvider() {
+ @Override
+ public Set<String> getClassDescriptors() {
+ return Collections.singleton(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()));
+ }
+
+ @Override
+ public ProgramResource getProgramResource(String descriptor) {
+ try {
+ return ProgramResource.fromBytes(
+ origin(clazz.getTypeName()),
+ Kind.CF,
+ ToolHelper.getClassAsBytes(clazz),
+ getClassDescriptors());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ }
+
+ public static class A {}
+
+ public static class B {}
+
+ public static class C {}
+}
diff --git a/src/test/java/com/android/tools/r8/DumpInputsTest.java b/src/test/java/com/android/tools/r8/DumpInputsTest.java
index 3c2d9b3..b8b3a04 100644
--- a/src/test/java/com/android/tools/r8/DumpInputsTest.java
+++ b/src/test/java/com/android/tools/r8/DumpInputsTest.java
@@ -10,10 +10,13 @@
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -35,7 +38,7 @@
}
@Test
- public void test() throws Exception {
+ public void testDumpToFile() throws Exception {
Path dump = temp.newFolder().toPath().resolve("dump.zip");
try {
testForExternalR8(parameters.getBackend())
@@ -44,26 +47,60 @@
.addProgramClasses(TestClass.class)
.compile();
} catch (AssertionError e) {
- assertTrue(Files.exists(dump));
- Path unzipped = temp.newFolder().toPath();
- ZipUtils.unzip(dump.toString(), unzipped.toFile());
- assertTrue(Files.exists(unzipped.resolve("program.jar")));
- assertTrue(Files.exists(unzipped.resolve("classpath.jar")));
- assertTrue(Files.exists(unzipped.resolve("proguard.config")));
- assertTrue(Files.exists(unzipped.resolve("r8-version")));
- Set<String> entries = new HashSet<>();
- ZipUtils.iter(
- unzipped.resolve("program.jar").toString(),
- (entry, input) -> entries.add(entry.getName()));
- assertTrue(
- entries.contains(
- DescriptorUtils.getClassFileName(
- DescriptorUtils.javaTypeToDescriptor(TestClass.class.getTypeName()))));
+ verifyDump(dump, false, true);
return;
}
fail("Expected external compilation to exit");
}
+ @Test
+ public void testDumpToDirectory() throws Exception {
+ Path dumpDir = temp.newFolder().toPath();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ // Setting a directory will allow compilation to continue.
+ // Ensure the compilation and run can actually succeed.
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ assertTrue(Files.isDirectory(dumpDir));
+ List<Path> paths = Files.walk(dumpDir, 1).collect(Collectors.toList());
+ boolean hasVerified = false;
+ for (Path path : paths) {
+ if (!path.equals(dumpDir)) {
+ // The non-external run here results in assert code calling application read.
+ verifyDump(path, false, false);
+ hasVerified = true;
+ }
+ }
+ assertTrue(hasVerified);
+ }
+
+ private void verifyDump(Path dumpFile, boolean hasClasspath, boolean hasProguardConfig)
+ throws IOException {
+ assertTrue(Files.exists(dumpFile));
+ Path unzipped = temp.newFolder().toPath();
+ ZipUtils.unzip(dumpFile.toString(), unzipped.toFile());
+ assertTrue(Files.exists(unzipped.resolve("r8-version")));
+ assertTrue(Files.exists(unzipped.resolve("program.jar")));
+ assertTrue(Files.exists(unzipped.resolve("library.jar")));
+ if (hasClasspath) {
+ assertTrue(Files.exists(unzipped.resolve("classpath.jar")));
+ }
+ if (hasProguardConfig) {
+ assertTrue(Files.exists(unzipped.resolve("proguard.config")));
+ }
+ Set<String> entries = new HashSet<>();
+ ZipUtils.iter(
+ unzipped.resolve("program.jar").toString(), (entry, input) -> entries.add(entry.getName()));
+ assertTrue(
+ entries.contains(
+ DescriptorUtils.getClassFileName(
+ DescriptorUtils.javaTypeToDescriptor(TestClass.class.getTypeName()))));
+ }
+
static class TestClass {
public static void main(String[] args) {
System.out.println("Hello, world");
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index fee1235..190e7ee 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -95,6 +95,7 @@
testForD8()
.setMinApi(parameters.getApiLevel())
.apply(this::configureProgram)
+ .setIncludeClassesChecksum(true)
.compile()
.run(parameters.getRuntime(), testClassName)
.assertSuccess()
diff --git a/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsFileWithNoClassesTest.java b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsFileWithNoClassesTest.java
new file mode 100644
index 0000000..57fe4b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsFileWithNoClassesTest.java
@@ -0,0 +1,77 @@
+// 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.dexfilemerger;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DexMergeChecksumsFileWithNoClassesTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public DexMergeChecksumsFileWithNoClassesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testChecksumWithNoClasses() throws Exception {
+ Path out1 =
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .setMode(CompilationMode.DEBUG)
+ .setIncludeClassesChecksum(true)
+ .setIntermediate(true)
+ .compile()
+ .inspect(this::checkContainsChecksums)
+ .writeToZip();
+
+ Path out2 =
+ testForD8()
+ .setMinApi(parameters.getApiLevel())
+ .setMode(CompilationMode.DEBUG)
+ .setIncludeClassesChecksum(true)
+ .addProgramClasses(TestClass.class)
+ .setIntermediate(true)
+ .compile()
+ .inspect(this::checkContainsChecksums)
+ .writeToZip();
+
+ testForD8()
+ .addProgramFiles(out1, out2)
+ .setIncludeClassesChecksum(true)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::checkContainsChecksums);
+ }
+
+ private void checkContainsChecksums(CodeInspector inspector) {
+ inspector.getMarkers().forEach(m -> assertTrue(m.getHasChecksums()));
+ // It may be prudent to check that the dex file also has the encoding string, but that is
+ // not easily accessed.
+ inspector.allClasses().forEach(c -> c.getDexClass().asProgramClass().getChecksum());
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsHighSortingStrings.java b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsHighSortingStrings.java
index e6a90ff..c5ad260 100644
--- a/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsHighSortingStrings.java
+++ b/src/test/java/com/android/tools/r8/dexfilemerger/DexMergeChecksumsHighSortingStrings.java
@@ -101,27 +101,28 @@
DexItemFactory factory = new DexItemFactory();
assertFalse(
- ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("\uDB3F\uDFFD")));
+ ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("\uDB3F\uDFFD")));
assertFalse(
- ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~\uDB3F\uDFFD\"")));
+ ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~\uDB3F\uDFFD\"")));
assertFalse(
- ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~\uDB3F\uDFFD")));
+ ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~~\uDB3F\uDFFD")));
assertFalse(
- ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~\uDB3F\uDFFD")));
- assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("\u007f")));
- assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~\u007f")));
- assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~\u007f")));
- assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~\u007f")));
- assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~|")));
+ ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~~~\uDB3F\uDFFD")));
+ assertFalse(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("\u007f")));
+ assertFalse(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~\u007f")));
+ assertFalse(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~~\u007f")));
+ assertFalse(
+ ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~~~\u007f")));
+ assertFalse(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~~~|")));
- assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~}")));
- assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~")));
- assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~}")));
- assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~")));
- assertTrue(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("}")));
+ assertTrue(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~~}")));
+ assertTrue(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~~")));
+ assertTrue(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~}")));
+ assertTrue(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~")));
+ assertTrue(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("}")));
// False negatives.
- assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~")));
- assertFalse(ClassesChecksum.definitelyPreceedChecksumMarker(factory.createString("~~~z")));
+ assertFalse(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~~~")));
+ assertFalse(ClassesChecksum.definitelyPrecedesChecksumMarker(factory.createString("~~~z")));
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index dcba861..48dcf36 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.nio.file.Path;
import java.util.List;
import org.junit.Test;
@@ -59,7 +60,7 @@
return buildParameters(
BooleanUtils.values(),
BooleanUtils.values(),
- getTestParameters().withAllRuntimes().build());
+ getTestParameters().withAllRuntimesAndApiLevels().build());
}
public Proto2ShrinkingTest(
@@ -78,12 +79,6 @@
.addKeepMainRule("proto2.TestClass")
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
.addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
- // TODO(b/112437944): Attempt to prove that DEFAULT_INSTANCE is non-null, such that the
- // following "assumenotnull" rule can be omitted.
- .addKeepRules(
- "-assumenosideeffects class " + EXT_C + " {",
- " private static final " + EXT_C + " DEFAULT_INSTANCE return 1..42;",
- "}")
.addOptionsModification(
options -> {
options.enableFieldBitAccessAnalysis = true;
@@ -94,7 +89,7 @@
.allowAccessModification(allowAccessModification)
.allowUnusedProguardConfigurationRules()
.minification(enableMinification)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
outputInspector -> {
@@ -133,11 +128,16 @@
"10",
"10");
+ DexItemFactory dexItemFactory = new DexItemFactory();
+ ProtoApplicationStats original = new ProtoApplicationStats(dexItemFactory, inputInspector);
+ ProtoApplicationStats actual =
+ new ProtoApplicationStats(dexItemFactory, result.inspector(), original);
+
+ assertEquals(
+ ImmutableSet.of(),
+ actual.getGeneratedExtensionRegistryStats().getSpuriouslyRetainedExtensionFields());
+
if (ToolHelper.isLocalDevelopment()) {
- DexItemFactory dexItemFactory = new DexItemFactory();
- ProtoApplicationStats original = new ProtoApplicationStats(dexItemFactory, inputInspector);
- ProtoApplicationStats actual =
- new ProtoApplicationStats(dexItemFactory, result.inspector(), original);
System.out.println(actual.getStats());
}
}
@@ -245,8 +245,8 @@
ImmutableList.of(FLAGGED_OFF_EXTENSION, HAS_NO_USED_EXTENSIONS, EXT_B, EXT_C);
for (String unusedExtensionName : unusedExtensionNames) {
assertThat(inputInspector.clazz(unusedExtensionName), isPresent());
- // TODO(b/143588134): Re-enable this assertion.
- // assertThat(outputInspector.clazz(unusedExtensionName), not(isPresent()));
+ assertThat(
+ unusedExtensionName, outputInspector.clazz(unusedExtensionName), not(isPresent()));
}
}
}
@@ -362,7 +362,7 @@
.allowAccessModification(allowAccessModification)
.allowUnusedProguardConfigurationRules()
.minification(enableMinification)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
inspector ->
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 42908d2..3c47534 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -536,7 +536,7 @@
@Test
public void testSelfOrderWithoutSubtypingInfo() {
DexType type = factory.createType("Lmy/Type;");
- appView.withSubtyping().appInfo().registerNewType(type, factory.objectType);
+ appView.withSubtyping().appInfo().registerNewTypeForTesting(type, factory.objectType);
TypeLatticeElement nonNullType = fromDexType(type, Nullability.definitelyNotNull(), appView);
ReferenceTypeLatticeElement nullableType =
nonNullType.asReferenceTypeLatticeElement().getOrCreateVariant(Nullability.maybeNull());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinitializerdefaults/NonFinalFieldWithDefaultValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinitializerdefaults/NonFinalFieldWithDefaultValueTest.java
new file mode 100644
index 0000000..c9033f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinitializerdefaults/NonFinalFieldWithDefaultValueTest.java
@@ -0,0 +1,122 @@
+// 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.ir.optimize.classinitializerdefaults;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class NonFinalFieldWithDefaultValueTest extends TestBase {
+
+ private final String EXPECTED_OUTPUT =
+ StringUtils.lines("Hello world (f1=1, f3=0)!", "1", "2", "3", "-1", "-2", "-3");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public NonFinalFieldWithDefaultValueTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8()
+ .addInnerClasses(NonFinalFieldWithDefaultValueTest.class)
+ .setMinApi(parameters.getApiLevel())
+ // Class initializer defaults optimization only runs in release mode.
+ .release()
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(NonFinalFieldWithDefaultValueTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+
+ // No instructions can read field f1 before it is assigned, and therefore, we can safely
+ // remove the static-put instruction and update the static value of the field.
+ FieldSubject f1FieldSubject = testClassSubject.uniqueFieldWithName("f1");
+ assertThat(f1FieldSubject, isPresent());
+ assertNotNull(f1FieldSubject.getField().getStaticValue());
+ assertTrue(f1FieldSubject.getField().getStaticValue().isDexValueInt());
+ assertEquals(1, f1FieldSubject.getField().getStaticValue().asDexValueInt().getValue());
+
+ // Field f3 is assigned after an instruction that could read it, and therefore, we cannot safely
+ // remove the static-put instruction and update the static value of the field.
+ FieldSubject f3FieldSubject = testClassSubject.uniqueFieldWithName("f3");
+ assertThat(f3FieldSubject, isPresent());
+ assertNotNull(f3FieldSubject.getField().getStaticValue());
+ assertTrue(f3FieldSubject.getField().getStaticValue().isDexValueInt());
+ assertEquals(0, f3FieldSubject.getField().getStaticValue().asDexValueInt().getValue());
+ }
+
+ static class TestClass {
+
+ static int f1 = 1;
+ static Greeter f2 = new Greeter(2);
+ static int f3 = 3;
+
+ public static void main(String[] args) {
+ System.out.println(f1);
+ System.out.println(f2.value);
+ System.out.println(f3);
+ updateFields();
+ System.out.println(f1);
+ System.out.println(f2.value);
+ System.out.println(f3);
+ }
+
+ @NeverInline
+ static void updateFields() {
+ f1 = -1;
+ f2.value = -2;
+ f3 = -3;
+ }
+ }
+
+ static class Greeter {
+
+ int value;
+
+ Greeter(int value) {
+ System.out.println("Hello world (f1=" + TestClass.f1 + ", f3=" + TestClass.f3 + ")!");
+ this.value = value;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest.java
new file mode 100644
index 0000000..25c2a61
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest.java
@@ -0,0 +1,77 @@
+// 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.ir.optimize.unusedarguments;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(UnusedArgumentsInMethodThatImplementsInterfaceMethodOnSubTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ // TODO(b/143934502): Should succeed with "Hello from A", "Hello from C".
+ .assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ for (I o : new I[] {new B(), new C()}) {
+ o.method(new Object());
+ }
+ }
+ }
+
+ interface I {
+
+ void method(Object o);
+ }
+
+ @NeverMerge
+ static class A {
+
+ public void method(Object unused) {
+ System.out.println("Hello from A");
+ }
+ }
+
+ static class B extends A implements I {}
+
+ static class C implements I {
+
+ @Override
+ public void method(Object o) {
+ if (o != null) {
+ System.out.println("Hello from C");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 99b2271..26f2030 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -98,9 +98,7 @@
}
private ClassBuilder(String name, String superName) {
- this.name = name;
- this.superName = superName;
- this.interfaces = ImmutableList.of();
+ this(name , superName, new String[0]);
}
private ClassBuilder(String name, String superName, String... interfaces) {
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java
new file mode 100644
index 0000000..442fda4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractInterfaceMethodsTest.java
@@ -0,0 +1,80 @@
+// 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.shaking.methods.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This tests is showing the issue filed in b/143590191. The expectations for the test should
+ * reflect the decisions to keep the interface method or not in the super interface.
+ */
+@RunWith(Parameterized.class)
+public class AbstractInterfaceMethodsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public AbstractInterfaceMethodsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testSingleInheritanceProguard()
+ throws CompilationFailedException, IOException, ExecutionException {
+ assumeTrue(parameters.isCfRuntime());
+ testForProguard()
+ .addProgramClasses(I.class, J.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(J.class, "void foo()")
+ .addKeepRules("-dontwarn")
+ .compile()
+ .inspect(AbstractInterfaceMethodsTest::inspectBaseInterfaceRemove);
+ }
+
+ @Test
+ public void testSingleInheritanceR8()
+ throws CompilationFailedException, IOException, ExecutionException {
+ // TODO(b/143590191): Fix expectation when resolved.
+ testForR8(parameters.getBackend())
+ .addProgramClasses(I.class, J.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMethodRules(J.class, "void foo()")
+ .compile()
+ .inspect(AbstractInterfaceMethodsTest::inspectBaseInterfaceRemove);
+ }
+
+ private static void inspectBaseInterfaceRemove(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(J.class);
+ assertThat(clazz, isPresent());
+ assertThat(clazz.uniqueMethodWithName("foo"), not(isPresent()));
+ assertThat(inspector.clazz(I.class), not(isPresent()));
+ }
+
+ public interface I {
+ void foo();
+ }
+
+ public interface J extends I {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractMethodsTest.java
rename to src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
index 808ae73..c6feb05 100644
--- a/src/test/java/com/android/tools/r8/shaking/methods/interfaces/AbstractMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/methods/interfaces/DefaultInterfaceMethodsTest.java
@@ -28,7 +28,7 @@
* reflect the decisions to keep the interface method or not in the super interface.
*/
@RunWith(Parameterized.class)
-public class AbstractMethodsTest extends TestBase {
+public class DefaultInterfaceMethodsTest extends TestBase {
private final TestParameters parameters;
@@ -37,7 +37,7 @@
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- public AbstractMethodsTest(TestParameters parameters) {
+ public DefaultInterfaceMethodsTest(TestParameters parameters) {
this.parameters = parameters;
}
@@ -51,7 +51,7 @@
.addKeepMethodRules(J.class, "void foo()")
.addKeepRules("-dontwarn")
.compile()
- .inspect(AbstractMethodsTest::inspectBaseInterfaceRemove);
+ .inspect(DefaultInterfaceMethodsTest::inspectBaseInterfaceRemove);
}
@Test
@@ -62,7 +62,7 @@
.setMinApi(parameters.getApiLevel())
.addKeepMethodRules(J.class, "void foo()")
.compile()
- .inspect(AbstractMethodsTest::inspectBaseInterfaceRemove);
+ .inspect(DefaultInterfaceMethodsTest::inspectBaseInterfaceRemove);
}
private static void inspectBaseInterfaceRemove(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index e769a54..8e2f554 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -49,6 +49,11 @@
}
@Override
+ public FieldSubject uniqueFieldWithFinalName(String name) {
+ return new AbsentFieldSubject();
+ }
+
+ @Override
public boolean isAbstract() {
throw new Unreachable("Cannot determine if an absent class is abstract");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 4ed548e..8b8f519 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -123,6 +123,8 @@
public abstract FieldSubject uniqueFieldWithName(String name);
+ public abstract FieldSubject uniqueFieldWithFinalName(String name);
+
public FoundClassSubject asFoundClassSubject() {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 2a91e4b..2bc1dd4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.cf.code.CfTryCatch;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
@@ -48,6 +49,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -391,6 +393,10 @@
}
}
+ public Collection<Marker> getMarkers() {
+ return dexItemFactory.extractMarkers();
+ }
+
// Build the generic signature using the current mapping if any.
class GenericSignatureGenerator implements GenericSignatureAction<String> {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
index c182f74..dc9940f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -26,6 +26,10 @@
return this;
}
+ public FoundFieldSubject asFoundFieldSubject() {
+ return null;
+ }
+
@Override
public boolean isFieldSubject() {
return true;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index d7e46b1..45276f4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -171,6 +171,18 @@
}
@Override
+ public FieldSubject uniqueFieldWithFinalName(String name) {
+ FieldSubject fieldSubject = null;
+ for (FoundFieldSubject candidate : allFields()) {
+ if (candidate.getFinalName().equals(name)) {
+ assert fieldSubject == null;
+ fieldSubject = candidate;
+ }
+ }
+ return fieldSubject != null ? fieldSubject : new AbsentFieldSubject();
+ }
+
+ @Override
public FoundClassSubject asFoundClassSubject() {
return this;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index a4d8210..f659abe 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -27,6 +27,11 @@
}
@Override
+ public FoundFieldSubject asFoundFieldSubject() {
+ return this;
+ }
+
+ @Override
public boolean isPublic() {
return dexField.accessFlags.isPublic();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java b/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java
index 4a0f63d..21e821d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/analysis/ProtoApplicationStats.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
@@ -96,11 +97,15 @@
}
}
- class GeneratedExtensionRegistryStats extends Stats {
+ public class GeneratedExtensionRegistryStats extends Stats {
final Set<DexMethod> findLiteExtensionByNumberMethods = Sets.newIdentityHashSet();
- final Set<DexType> retainedExtensions = Sets.newIdentityHashSet();
- final Set<DexType> spuriouslyRetainedExtensions = Sets.newIdentityHashSet();
+ final Set<DexField> retainedExtensionFields = Sets.newIdentityHashSet();
+ final Set<DexField> spuriouslyRetainedExtensionFields = Sets.newIdentityHashSet();
+
+ public Set<DexField> getSpuriouslyRetainedExtensionFields() {
+ return spuriouslyRetainedExtensionFields;
+ }
String getStats(
GeneratedExtensionRegistryStats baseline, GeneratedExtensionRegistryStats original) {
@@ -109,8 +114,8 @@
" # findLiteExtensionByNumber() methods: "
+ progress(this, baseline, original, x -> x.findLiteExtensionByNumberMethods),
" # retained extensions: "
- + progress(this, baseline, original, x -> x.retainedExtensions),
- " # spuriously retained extensions: " + spuriouslyRetainedExtensions.size());
+ + progress(this, baseline, original, x -> x.retainedExtensionFields),
+ " # spuriously retained extension fields: " + spuriouslyRetainedExtensionFields.size());
}
}
@@ -181,8 +186,12 @@
}
FoundClassSubject extensionClassSubject =
inspector.clazz(field.holder.toSourceString()).asFoundClassSubject();
- generatedExtensionRegistryStats.retainedExtensions.add(
- extensionClassSubject.getOriginalDexType(dexItemFactory));
+ FoundFieldSubject extensionFieldSubject =
+ extensionClassSubject
+ .uniqueFieldWithFinalName(field.name.toSourceString())
+ .asFoundFieldSubject();
+ generatedExtensionRegistryStats.retainedExtensionFields.add(
+ extensionFieldSubject.getOriginalDexField(dexItemFactory));
}
}
}
@@ -203,15 +212,28 @@
}
if (original != null) {
- for (DexType extensionType : original.generatedExtensionRegistryStats.retainedExtensions) {
- if (!generatedExtensionRegistryStats.retainedExtensions.contains(extensionType)
- && inspector.clazz(extensionType.toSourceString()).isPresent()) {
- generatedExtensionRegistryStats.spuriouslyRetainedExtensions.add(extensionType);
+ for (DexField extensionField :
+ original.generatedExtensionRegistryStats.retainedExtensionFields) {
+ if (generatedExtensionRegistryStats.retainedExtensionFields.contains(extensionField)) {
+ continue;
+ }
+ ClassSubject classSubject = inspector.clazz(extensionField.holder.toSourceString());
+ if (!classSubject.isPresent()) {
+ continue;
+ }
+ FieldSubject fieldSubject =
+ classSubject.uniqueFieldWithName(extensionField.name.toSourceString());
+ if (fieldSubject.isPresent()) {
+ generatedExtensionRegistryStats.spuriouslyRetainedExtensionFields.add(extensionField);
}
}
}
}
+ public GeneratedExtensionRegistryStats getGeneratedExtensionRegistryStats() {
+ return generatedExtensionRegistryStats;
+ }
+
public String getStats() {
return StringUtils.lines(
enumStats.getStats(null, original.enumStats),
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 0048d52..0f129ec 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -80,20 +80,7 @@
version_diff_output = subprocess.check_output([
'git', 'diff', '%s..HEAD' % commithash])
- invalid = version_change_diff(version_diff_output, "master", version)
- if invalid:
- print "Unexpected diff:"
- print "=" * 80
- print version_diff_output
- print "=" * 80
- accept_string = 'THE DIFF IS OK!'
- input = raw_input(
- "Accept the additonal diff as part of the release? "
- "Type '%s' to accept: " % accept_string)
- if input != accept_string:
- print "You did not type '%s'" % accept_string
- print 'Aborting dev release for %s' % version
- sys.exit(1)
+ validate_version_change_diff(version_diff_output, "master", version)
# Double check that we want to push the release.
if not args.dry_run:
@@ -104,10 +91,7 @@
maybe_check_call(args, [
'git', 'push', 'origin', 'HEAD:%s' % R8_DEV_BRANCH])
- maybe_check_call(args, [
- 'git', 'tag', '-a', version, '-m', '"%s"' % version])
- maybe_check_call(args, [
- 'git', 'push', 'origin', 'refs/tags/%s' % version])
+ maybe_tag(args, version)
return "%s dev version %s from hash %s" % (
'DryRun: omitted publish of' if args.dry_run else 'Published',
@@ -117,6 +101,12 @@
return make_release
+def maybe_tag(args, version):
+ maybe_check_call(args, [
+ 'git', 'tag', '-a', version, '-m', '"%s"' % version])
+ maybe_check_call(args, [
+ 'git', 'push', 'origin', 'refs/tags/%s' % version])
+
def version_change_diff(diff, old_version, new_version):
invalid_line = None
for line in diff.splitlines():
@@ -128,6 +118,22 @@
invalid_line = line
return invalid_line
+def validate_version_change_diff(version_diff_output, old_version, new_version):
+ invalid = version_change_diff(version_diff_output, old_version, new_version)
+ if invalid:
+ print "Unexpected diff:"
+ print "=" * 80
+ print version_diff_output
+ print "=" * 80
+ accept_string = 'THE DIFF IS OK!'
+ input = raw_input(
+ "Accept the additonal diff as part of the release? "
+ "Type '%s' to accept: " % accept_string)
+ if input != accept_string:
+ print "You did not type '%s'" % accept_string
+ print 'Aborting dev release for %s' % version
+ sys.exit(1)
+
def maybe_check_call(args, cmd):
if args.dry_run:
@@ -344,6 +350,62 @@
return release_google3
+def prepare_branch(args):
+ branch_version = args.new_dev_branch[0]
+ commithash = args.new_dev_branch[1]
+
+ current_semver = utils.check_basic_semver_version(
+ R8_DEV_BRANCH, ", current release branch version should be x.y", 2)
+ semver = utils.check_basic_semver_version(
+ branch_version, ", release branch version should be x.y", 2)
+ if not semver.larger_than(current_semver):
+ print ('New branch version "'
+ + branch_version
+ + '" must be strictly larger than the current "'
+ + R8_DEV_BRANCH
+ + '"')
+ sys.exit(1)
+
+ def make_branch(options):
+ subprocess.check_call(['git', 'branch', branch_version, commithash])
+
+ subprocess.check_call(['git', 'checkout', branch_version])
+
+ # Rewrite the version, commit and validate.
+ old_version = 'master'
+ full_version = branch_version + '.0-dev'
+ version_prefix = 'LABEL = "'
+ sed(version_prefix + old_version,
+ version_prefix + full_version,
+ R8_VERSION_FILE)
+
+ subprocess.check_call([
+ 'git', 'commit', '-a', '-m', 'Version %s' % full_version])
+
+ version_diff_output = subprocess.check_output([
+ 'git', 'diff', '%s..HEAD' % commithash])
+
+ validate_version_change_diff(version_diff_output, old_version, full_version)
+
+ # Double check that we want to create a new release branch.
+ if not options.dry_run:
+ input = raw_input('Create new branch for %s [y/N]:' % branch_version)
+ if input != 'y':
+ print 'Aborting new branch for %s' % branch_version
+ sys.exit(1)
+
+ maybe_check_call(options, [
+ 'git', 'push', 'origin', 'HEAD:%s' % branch_version])
+ maybe_tag(options, full_version)
+
+ # TODO(sgjesse): Automate this part as well!
+ print ('REMEMBER TO UPDATE R8_DEV_BRANCH in tools/r8_release.py to "'
+ + full_version
+ + '"!!!')
+
+ return make_branch
+
+
def parse_options():
result = argparse.ArgumentParser(description='Release r8')
group = result.add_mutually_exclusive_group()
@@ -351,6 +413,10 @@
help='The hash to use for the new dev version of R8')
group.add_argument('--version',
help='The new version of R8 (e.g., 1.4.51)')
+ group.add_argument('--new-dev-branch',
+ nargs=2,
+ metavar=('VERSION', 'HASH'),
+ help='Create a new branch starting a version line')
result.add_argument('--no-sync', '--no_sync',
default=False,
action='store_true',
@@ -389,6 +455,12 @@
args = parse_options()
targets_to_run = []
+ if args.new_dev_branch:
+ if args.google3 or args.studio or args.aosp:
+ print 'Cannot create a branch and roll at the same time.'
+ sys.exit(1)
+ targets_to_run.append(prepare_branch(args))
+
if args.dev_release:
if args.google3 or args.studio or args.aosp:
print 'Cannot create a dev release and roll at the same time.'
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 8d7264b..d45364d 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -412,7 +412,8 @@
print 'Running with wrapper that will kill java after'
def __exit__(self, *_):
- subprocess.call(['pkill', 'java'])
+ if self.active:
+ subprocess.call(['pkill', 'java'])
def GetAllApps():
apps = []
@@ -623,39 +624,45 @@
apk_dest = None
result = {}
- try:
- out_dir = os.path.join(checkout_dir, 'out', shrinker)
- (apk_dest, profile_dest_dir, proguard_config_file) = \
- BuildAppWithShrinker(
- app, repo, shrinker, checkout_dir, out_dir, temp_dir,
- options)
- dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
- result['apk_dest'] = apk_dest
- result['build_status'] = 'success'
- result['dex_size'] = dex_size
- result['profile_dest_dir'] = profile_dest_dir
+ proguard_config_file = None
+ if not options.r8_compilation_steps_only:
+ try:
+ out_dir = os.path.join(checkout_dir, 'out', shrinker)
+ (apk_dest, profile_dest_dir, res_proguard_config_file) = \
+ BuildAppWithShrinker(
+ app, repo, shrinker, checkout_dir, out_dir, temp_dir,
+ options)
+ proguard_config_file = res_proguard_config_file
+ dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
+ result['apk_dest'] = apk_dest
+ result['build_status'] = 'success'
+ result['dex_size'] = dex_size
+ result['profile_dest_dir'] = profile_dest_dir
- profile = as_utils.ParseProfileReport(profile_dest_dir)
- result['profile'] = {
- task_name:duration for task_name, duration in profile.iteritems()
- if as_utils.IsGradleCompilerTask(task_name, shrinker)}
- except Exception as e:
- warn('Failed to build {} with {}'.format(app.name, shrinker))
- if e:
- print('Error: ' + str(e))
- result['build_status'] = 'failed'
+ profile = as_utils.ParseProfileReport(profile_dest_dir)
+ result['profile'] = {
+ task_name:duration for task_name, duration in profile.iteritems()
+ if as_utils.IsGradleCompilerTask(task_name, shrinker)}
+ except Exception as e:
+ warn('Failed to build {} with {}'.format(app.name, shrinker))
+ if e:
+ print('Error: ' + str(e))
+ result['build_status'] = 'failed'
if result.get('build_status') == 'success':
if options.monkey:
result['monkey_status'] = 'success' if RunMonkey(
app, options, apk_dest) else 'failed'
+ if (result.get('build_status') == 'success'
+ or options.r8_compilation_steps_only):
if 'r8' in shrinker and options.r8_compilation_steps > 1:
result['recompilation_results'] = \
ComputeRecompilationResults(
app, repo, options, checkout_dir, temp_dir, shrinker,
proguard_config_file)
+ if result.get('build_status') == 'success':
if options.run_tests and app.has_instrumentation_tests:
result['instrumentation_test_results'] = \
ComputeInstrumentationTestResults(
@@ -851,15 +858,17 @@
}
recompilation_results.append(recompilation_result)
- # Sanity check that keep rules have changed.
- with open(ext_proguard_config_file) as new:
- with open(proguard_config_file) as old:
- assert(
- sum(1 for line in new
- if line.strip() and '-printconfiguration' not in line)
- >
- sum(1 for line in old
- if line.strip() and '-printconfiguration' not in line))
+ # Sanity check that keep rules have changed. If we are only doing
+ # recompilation, the passed in proguard_config_file is None.
+ if proguard_config_file:
+ with open(ext_proguard_config_file) as new:
+ with open(proguard_config_file) as old:
+ assert(
+ sum(1 for line in new
+ if line.strip() and '-printconfiguration' not in line)
+ >
+ sum(1 for line in old
+ if line.strip() and '-printconfiguration' not in line))
# Extract min-sdk and target-sdk
(min_sdk, compile_sdk) = \
@@ -1017,22 +1026,24 @@
continue
result = result_per_shrinker.get(shrinker)
build_status = result.get('build_status')
- if build_status != 'success':
+ if build_status != 'success' and build_status is not None:
app_error = True
warn(' {}: {}'.format(shrinker, build_status))
- else:
- print(' {}:'.format(shrinker))
- dex_size = result.get('dex_size')
- msg = ' dex size: {}'.format(dex_size)
- if dex_size != proguard_dex_size and proguard_dex_size >= 0:
- msg = '{} ({}, {})'.format(
- msg, dex_size - proguard_dex_size,
- PercentageDiffAsString(proguard_dex_size, dex_size))
- success(msg) if dex_size < proguard_dex_size else warn(msg)
- else:
- print(msg)
+ continue
- profile = result.get('profile')
+ print(' {}:'.format(shrinker))
+ dex_size = result.get('dex_size')
+ msg = ' dex size: {}'.format(dex_size)
+ if dex_size != proguard_dex_size and proguard_dex_size >= 0:
+ msg = '{} ({}, {})'.format(
+ msg, dex_size - proguard_dex_size,
+ PercentageDiffAsString(proguard_dex_size, dex_size))
+ success(msg) if dex_size < proguard_dex_size else warn(msg)
+ else:
+ print(msg)
+
+ profile = result.get('profile')
+ if profile:
duration = sum(profile.values())
msg = ' performance: {}s'.format(duration)
if duration != proguard_duration and proguard_duration > 0:
@@ -1046,53 +1057,53 @@
for task_name, task_duration in profile.iteritems():
print(' {}: {}s'.format(task_name, task_duration))
- if options.monkey:
- monkey_status = result.get('monkey_status')
- if monkey_status != 'success':
- app_error = True
- warn(' monkey: {}'.format(monkey_status))
- else:
- success(' monkey: {}'.format(monkey_status))
+ if options.monkey:
+ monkey_status = result.get('monkey_status')
+ if monkey_status != 'success':
+ app_error = True
+ warn(' monkey: {}'.format(monkey_status))
+ else:
+ success(' monkey: {}'.format(monkey_status))
- recompilation_results = result.get('recompilation_results', [])
- i = 0
- for recompilation_result in recompilation_results:
- build_status = recompilation_result.get('build_status')
- if build_status != 'success':
- app_error = True
- print(' recompilation #{}: {}'.format(i, build_status))
- else:
- dex_size = recompilation_result.get('dex_size')
- print(' recompilation #{}'.format(i))
- print(' dex size: {}'.format(dex_size))
- if options.monkey:
- monkey_status = recompilation_result.get('monkey_status')
- msg = ' monkey: {}'.format(monkey_status)
- if monkey_status == 'success':
- success(msg)
- elif monkey_status == 'skipped':
- print(msg)
- else:
- warn(msg)
- i += 1
+ recompilation_results = result.get('recompilation_results', [])
+ i = 0
+ for recompilation_result in recompilation_results:
+ build_status = recompilation_result.get('build_status')
+ if build_status != 'success':
+ app_error = True
+ print(' recompilation #{}: {}'.format(i, build_status))
+ else:
+ dex_size = recompilation_result.get('dex_size')
+ print(' recompilation #{}'.format(i))
+ print(' dex size: {}'.format(dex_size))
+ if options.monkey:
+ monkey_status = recompilation_result.get('monkey_status')
+ msg = ' monkey: {}'.format(monkey_status)
+ if monkey_status == 'success':
+ success(msg)
+ elif monkey_status == 'skipped':
+ print(msg)
+ else:
+ warn(msg)
+ i += 1
- if options.run_tests and 'instrumentation_test_results' in result:
- instrumentation_test_results = \
- result.get('instrumentation_test_results')
- succeeded = (
- instrumentation_test_results.get('failures')
- + instrumentation_test_results.get('errors')
- + instrumentation_test_results.get('skipped')) == 0
- if succeeded:
- success(' tests: succeeded')
- else:
- app_error = True
- warn(
- ' tests: failed (failures: {}, errors: {}, skipped: {})'
- .format(
- instrumentation_test_results.get('failures'),
- instrumentation_test_results.get('errors'),
- instrumentation_test_results.get('skipped')))
+ if options.run_tests and 'instrumentation_test_results' in result:
+ instrumentation_test_results = \
+ result.get('instrumentation_test_results')
+ succeeded = (
+ instrumentation_test_results.get('failures')
+ + instrumentation_test_results.get('errors')
+ + instrumentation_test_results.get('skipped')) == 0
+ if succeeded:
+ success(' tests: succeeded')
+ else:
+ app_error = True
+ warn(
+ ' tests: failed (failures: {}, errors: {}, skipped: {})'
+ .format(
+ instrumentation_test_results.get('failures'),
+ instrumentation_test_results.get('errors'),
+ instrumentation_test_results.get('skipped')))
return app_error
@@ -1172,6 +1183,10 @@
help='Number of times R8 should be run on each app',
default=2,
type=int)
+ result.add_option('--r8-compilation-steps-only', '--r8_compilation_steps_only',
+ help='Specify to only run compilation steps',
+ default=False,
+ action='store_true')
result.add_option('--run-tests', '--run_tests',
help='Whether to run instrumentation tests',
default=False,
@@ -1215,6 +1230,8 @@
warn('Skipping shrinker r8-nolib-full because a specific version '
+ 'of r8 was specified')
options.shrinker.remove('r8-nolib-full')
+ assert not options.r8_compilation_steps_only \
+ or options.r8_compilation_steps > 1
return (options, args)
def clone_repositories(quiet):
diff --git a/tools/utils.py b/tools/utils.py
index 67ead07..733d190 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -579,12 +579,46 @@
check_basic_semver_version(version, 'in ' + DESUGAR_CONFIGURATION)
return version
+class SemanticVersion:
+ def __init__(self, major, minor, patch):
+ self.major = major
+ self.minor = minor
+ self.patch = patch
+ # Build metadata currently not suppported
+
+ def larger_than(self, other):
+ if self.major > other.major:
+ return True
+ if self.major == other.major and self.minor > other.minor:
+ return True
+ if self.patch:
+ return (self.major == other.major
+ and self.minor == other.minor
+ and self.patch > other.patch)
+ else:
+ return False
+
+
# Check that the passed string is formatted as a basic semver version (x.y.z)
# See https://semver.org/.
-def check_basic_semver_version(version, error_context = ''):
- reg = re.compile('^([0-9]+)\\.([0-9]+)\\.([0-9]+)$')
- if not reg.match(version):
+def check_basic_semver_version(version, error_context = '', components = 3):
+ regexp = '^'
+ for x in range(components):
+ regexp += '([0-9]+)'
+ if x < components - 1:
+ regexp += '\\.'
+ regexp += '$'
+ reg = re.compile(regexp)
+ match = reg.match(version)
+ if not match:
raise Exception("Invalid version '"
+ version
+ "'"
+ (' ' + error_context) if len(error_context) > 0 else '')
+ if components == 2:
+ return SemanticVersion(int(match.group(1)), int(match.group(2)), None)
+ elif components == 3:
+ return SemanticVersion(
+ int(match.group(1)), int(match.group(2)), int(match.group(3)))
+ else:
+ raise Exception('Argument "components" must be 2 or 3')