Merge commit '38eb13a53d863b5d18cee58605bb2295b6aeb547' into dev-release
diff --git a/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
index a4af022..c856d87 100644
--- a/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
+++ b/src/main/java/com/android/tools/r8/BackportedMethodListCommand.java
@@ -104,7 +104,7 @@
InternalOptions getInternalOptions() {
InternalOptions options = new InternalOptions(factory, getReporter());
- options.minApiLevel = minApiLevel;
+ options.minApiLevel = AndroidApiLevel.getAndroidApiLevel(minApiLevel);
options.desugaredLibraryConfiguration = desugaredLibraryConfiguration;
return options;
}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 8863e35..1ef4a6e 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -458,7 +458,7 @@
internal.mainDexListConsumer = getMainDexListConsumer();
internal.minimalMainDex = internal.debug || minimalMainDex;
internal.enableMainDexListCheck = enableMainDexListCheck;
- internal.minApiLevel = getMinApiLevel();
+ internal.minApiLevel = AndroidApiLevel.getAndroidApiLevel(getMinApiLevel());
internal.intermediate = intermediate;
internal.readCompileTimeAnnotations = intermediate;
internal.desugarGraphConsumer = desugarGraphConsumer;
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 9492e12..b21bb03 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -98,11 +98,10 @@
}
}
- private static Collection<Marker> extractMarker(AndroidApp app)
- throws IOException, ExecutionException {
+ private static Collection<Marker> extractMarker(AndroidApp app) throws IOException {
InternalOptions options = new InternalOptions();
options.skipReadingDexCode = true;
- options.minApiLevel = AndroidApiLevel.P.getLevel();
+ options.minApiLevel = AndroidApiLevel.P;
DexApplication dexApp = new ApplicationReader(app, options, new Timing("ExtractMarker")).read();
return dexApp.dexItemFactory.extractMarkers();
}
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index fc9a567..9ec502c 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.inspector.Inspector;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
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.AssertionConfigurationWithDefault;
import com.android.tools.r8.utils.DumpInputFlags;
@@ -160,7 +161,7 @@
internal.debug = getMode() == CompilationMode.DEBUG;
assert internal.mainDexListConsumer == null;
assert !internal.minimalMainDex;
- internal.minApiLevel = getMinApiLevel();
+ internal.minApiLevel = AndroidApiLevel.getAndroidApiLevel(getMinApiLevel());
assert !internal.intermediate;
assert internal.readCompileTimeAnnotations;
internal.programConsumer = getProgramConsumer();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7aed2af..1a78e63 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -33,7 +33,8 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.GenericSignatureTypeVariableRemover;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder;
+import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
import com.android.tools.r8.graph.PrunedItems;
@@ -105,7 +106,6 @@
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.synthesis.SyntheticFinalization;
import com.android.tools.r8.synthesis.SyntheticItems;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.CollectionUtils;
@@ -332,7 +332,7 @@
options.itemFactory, options.getProguardConfiguration().getRules())) {
synthesizedProguardRules.add(
ProguardConfigurationUtils.buildAssumeNoSideEffectsRuleForApiLevel(
- options.itemFactory, AndroidApiLevel.getAndroidApiLevel(options.minApiLevel)));
+ options.itemFactory, options.minApiLevel));
}
}
SubtypingInfo subtypingInfo = new SubtypingInfo(appView);
@@ -377,9 +377,14 @@
appView.withGeneratedExtensionRegistryShrinker(
shrinker -> shrinker.run(Mode.INITIAL_TREE_SHAKING));
- // Build enclosing information and type-paramter information before pruning.
- GenericSignatureTypeVariableRemover typeVariableRemover =
- GenericSignatureTypeVariableRemover.create(appView, appView.appInfo().classes());
+ // Build enclosing information and type-parameter information before pruning.
+ // TODO(b/187922482): Only consider referenced classes.
+ GenericSignatureContextBuilder genericContextBuilder =
+ GenericSignatureContextBuilder.create(appView.appInfo().classes());
+
+ // Compute if all signatures are valid before modifying them.
+ GenericSignatureCorrectnessHelper.createForInitialCheck(appView, genericContextBuilder)
+ .run(appView.appInfo().classes());
TreePruner pruner = new TreePruner(appViewWithLiveness);
DirectMappedDexApplication prunedApp = pruner.run(executorService);
@@ -404,8 +409,7 @@
annotationRemoverBuilder
.build(appViewWithLiveness, removedClasses);
annotationRemover.ensureValid().run();
- typeVariableRemover.removeDeadGenericSignatureTypeVariables(appView);
- new GenericSignatureRewriter(appView, NamingLens.getIdentityLens())
+ new GenericSignatureRewriter(appView, NamingLens.getIdentityLens(), genericContextBuilder)
.run(appView.appInfo().classes(), executorService);
}
} finally {
@@ -505,7 +509,7 @@
}
HorizontalClassMerger.createForInitialClassMerging(appViewWithLiveness)
- .runIfNecessary(runtimeTypeCheckInfo, timing);
+ .runIfNecessary(runtimeTypeCheckInfo, executorService, timing);
}
// Clear traced methods roots to not hold on to the main dex live method set.
@@ -597,8 +601,8 @@
shrinker -> shrinker.run(enqueuer.getMode()),
DefaultTreePrunerConfiguration.getInstance());
- GenericSignatureTypeVariableRemover typeVariableRemover =
- GenericSignatureTypeVariableRemover.create(appView, appView.appInfo().classes());
+ GenericSignatureContextBuilder genericContextBuilder =
+ GenericSignatureContextBuilder.create(appView.appInfo().classes());
TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
DirectMappedDexApplication application = pruner.run(executorService);
@@ -639,7 +643,10 @@
AnnotationRemover.builder()
.build(appView.withLiveness(), removedClasses)
.run();
- typeVariableRemover.removeDeadGenericSignatureTypeVariables(appView);
+ new GenericSignatureRewriter(
+ appView, NamingLens.getIdentityLens(), genericContextBuilder)
+ .run(appView.appInfo().classes(), executorService);
+
// Synthesize fields for triggering class initializers.
new ClassInitFieldSynthesizer(appViewWithLiveness).run(executorService);
}
@@ -714,6 +721,10 @@
SyntheticFinalization.finalizeWithClassHierarchy(appView);
}
+ // Clear the reference type lattice element cache. This is required since class merging may
+ // need to build IR.
+ appView.dexItemFactory().clearTypeElementsCache();
+
// Run horizontal class merging. This runs even if shrinking is disabled to ensure synthetics
// are always merged.
HorizontalClassMerger.createForFinalClassMerging(appView)
@@ -721,6 +732,7 @@
classMergingEnqueuerExtensionBuilder != null
? classMergingEnqueuerExtensionBuilder.build(appView.graphLens())
: null,
+ executorService,
timing);
// Perform minification.
@@ -787,6 +799,16 @@
options.syntheticProguardRulesConsumer.accept(synthesizedProguardRules);
}
+ assert appView.checkForTesting(
+ () ->
+ !options.isShrinking()
+ || GenericSignatureCorrectnessHelper.createForVerification(
+ appView,
+ GenericSignatureContextBuilder.create(appView.appInfo().classes()))
+ .run(appView.appInfo().classes())
+ .isValid())
+ : "Could not validate generic signatures";
+
NamingLens prefixRewritingNamingLens =
PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
@@ -892,31 +914,27 @@
private static boolean verifyMovedMethodsHaveOriginalMethodPosition(
AppView<?> appView, DirectMappedDexApplication application) {
application
- .classes()
+ .classesWithDeterministicOrder()
.forEach(
- clazz -> {
- clazz.forEachProgramMethod(
- method -> {
- DexMethod originalMethod =
- appView.graphLens().getOriginalMethodSignature(method.getReference());
- if (originalMethod != method.getReference()) {
- DexMethod originalMethod2 =
+ clazz ->
+ clazz.forEachProgramMethod(
+ method -> {
+ DexMethod originalMethod =
appView.graphLens().getOriginalMethodSignature(method.getReference());
- appView.graphLens().getOriginalMethodSignature(method.getReference());
- DexEncodedMethod definition = method.getDefinition();
- Code code = definition.getCode();
- if (code == null) {
- return;
+ if (originalMethod != method.getReference()) {
+ DexEncodedMethod definition = method.getDefinition();
+ Code code = definition.getCode();
+ if (code == null) {
+ return;
+ }
+ if (code.isCfCode()) {
+ assert verifyOriginalMethodInPosition(code.asCfCode(), originalMethod);
+ } else {
+ assert code.isDexCode();
+ assert verifyOriginalMethodInDebugInfo(code.asDexCode(), originalMethod);
+ }
}
- if (code.isCfCode()) {
- assert verifyOriginalMethodInPosition(code.asCfCode(), originalMethod);
- } else {
- assert code.isDexCode();
- assert verifyOriginalMethodInDebugInfo(code.asDexCode(), originalMethod);
- }
- }
- });
- });
+ }));
return true;
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4fdb44d..7bb927c 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -833,7 +833,7 @@
assert !internal.debug;
internal.debug = getMode() == CompilationMode.DEBUG;
internal.programConsumer = getProgramConsumer();
- internal.minApiLevel = getMinApiLevel();
+ internal.minApiLevel = AndroidApiLevel.getAndroidApiLevel(getMinApiLevel());
internal.desugarState = getDesugarState();
assert internal.isShrinking() == getEnableTreeShaking();
assert internal.isMinifying() == getEnableMinification();
diff --git a/src/main/java/com/android/tools/r8/androidapi/AvailableApiExceptions.java b/src/main/java/com/android/tools/r8/androidapi/AvailableApiExceptions.java
index c44f1f4..606d653 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AvailableApiExceptions.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AvailableApiExceptions.java
@@ -26,7 +26,7 @@
private final Set<DexType> exceptions;
public AvailableApiExceptions(InternalOptions options) {
- assert options.minApiLevel < AndroidApiLevel.L.getLevel();
+ assert options.minApiLevel.isLessThan(AndroidApiLevel.L);
exceptions = build(options.itemFactory, options.minApiLevel);
}
@@ -35,9 +35,9 @@
}
/** The content of this method can be regenerated with GenerateAvailableExceptions.main. */
- public static Set<DexType> build(DexItemFactory factory, int minApiLevel) {
+ public static Set<DexType> build(DexItemFactory factory, AndroidApiLevel minApiLevel) {
Set<DexType> types = SetUtils.newIdentityHashSet(333);
- if (minApiLevel >= 1) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.B)) {
types.add(factory.createType("Landroid/app/PendingIntent$CanceledException;"));
types.add(factory.createType("Landroid/content/ActivityNotFoundException;"));
types.add(factory.createType("Landroid/content/IntentFilter$MalformedMimeTypeException;"));
@@ -305,17 +305,17 @@
types.add(factory.createType("Lorg/xml/sax/SAXParseException;"));
types.add(factory.createType("Lorg/xmlpull/v1/XmlPullParserException;"));
}
- if (minApiLevel >= 4) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.D)) {
types.add(factory.createType("Landroid/content/IntentSender$SendIntentException;"));
}
- if (minApiLevel >= 5) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.E)) {
types.add(factory.createType("Landroid/accounts/AccountsException;"));
types.add(factory.createType("Landroid/accounts/AuthenticatorException;"));
types.add(factory.createType("Landroid/accounts/NetworkErrorException;"));
types.add(factory.createType("Landroid/accounts/OperationCanceledException;"));
types.add(factory.createType("Landroid/content/OperationApplicationException;"));
}
- if (minApiLevel >= 8) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.F)) {
types.add(factory.createType("Ljavax/xml/datatype/DatatypeConfigurationException;"));
types.add(factory.createType("Ljavax/xml/transform/TransformerConfigurationException;"));
types.add(factory.createType("Ljavax/xml/transform/TransformerException;"));
@@ -326,7 +326,7 @@
types.add(factory.createType("Ljavax/xml/xpath/XPathFunctionException;"));
types.add(factory.createType("Lorg/w3c/dom/ls/LSException;"));
}
- if (minApiLevel >= 9) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.G)) {
types.add(factory.createType("Landroid/net/sip/SipException;"));
types.add(factory.createType("Landroid/nfc/FormatException;"));
types.add(factory.createType("Ljava/io/IOError;"));
@@ -346,10 +346,10 @@
types.add(factory.createType("Ljava/util/ServiceConfigurationError;"));
types.add(factory.createType("Ljava/util/zip/ZipError;"));
}
- if (minApiLevel >= 10) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.G_MR1)) {
types.add(factory.createType("Landroid/nfc/TagLostException;"));
}
- if (minApiLevel >= 11) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.H)) {
types.add(factory.createType("Landroid/app/Fragment$InstantiationException;"));
types.add(factory.createType("Landroid/database/sqlite/SQLiteAccessPermException;"));
types.add(
@@ -372,28 +372,28 @@
types.add(factory.createType("Landroid/util/MalformedJsonException;"));
types.add(factory.createType("Landroid/view/KeyCharacterMap$UnavailableException;"));
}
- if (minApiLevel >= 14) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.I)) {
types.add(factory.createType("Landroid/security/KeyChainException;"));
types.add(factory.createType("Landroid/util/NoSuchPropertyException;"));
}
- if (minApiLevel >= 15) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.I_MR1)) {
types.add(factory.createType("Landroid/os/TransactionTooLargeException;"));
}
- if (minApiLevel >= 16) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.J)) {
types.add(factory.createType("Landroid/media/MediaCodec$CryptoException;"));
types.add(factory.createType("Landroid/media/MediaCryptoException;"));
types.add(factory.createType("Landroid/os/OperationCanceledException;"));
}
- if (minApiLevel >= 17) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.J_MR1)) {
types.add(factory.createType("Landroid/view/WindowManager$InvalidDisplayException;"));
}
- if (minApiLevel >= 18) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.J_MR2)) {
types.add(factory.createType("Landroid/media/DeniedByServerException;"));
types.add(factory.createType("Landroid/media/MediaDrmException;"));
types.add(factory.createType("Landroid/media/NotProvisionedException;"));
types.add(factory.createType("Landroid/media/UnsupportedSchemeException;"));
}
- if (minApiLevel >= 19) {
+ if (minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.K)) {
types.add(factory.createType("Landroid/media/ResourceBusyException;"));
types.add(
factory.createType("Landroid/os/ParcelFileDescriptor$FileDescriptorDetachedException;"));
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 7b6c2e7..625430b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -144,7 +144,9 @@
}
}
- private Invoke.Type getInvokeType(DexClassAndMethod context) {
+ // We should avoid interpreting a CF invoke using DEX semantics.
+ @Deprecated
+ public Invoke.Type getInvokeType(DexClassAndMethod context) {
switch (opcode) {
case Opcodes.INVOKEINTERFACE:
return Type.INTERFACE;
@@ -171,10 +173,12 @@
return getMethod().isInstanceInitializer(dexItemFactory);
}
+ // We should avoid interpreting a CF invoke using DEX semantics.
+ @Deprecated
public boolean isInvokeSuper(DexType clazz) {
- return opcode == Opcodes.INVOKESPECIAL &&
- method.holder != clazz &&
- !method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
+ return opcode == Opcodes.INVOKESPECIAL
+ && method.holder != clazz
+ && !method.name.toString().equals(Constants.INSTANCE_INITIALIZER_NAME);
}
@Override
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 e0c1cc2..31d40bb 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -261,7 +261,7 @@
return true;
}
AndroidApiLevel nativeMultiDex = AndroidApiLevel.L;
- if (options.minApiLevel < nativeMultiDex.getLevel()) {
+ if (options.minApiLevel.isLessThan(nativeMultiDex)) {
return true;
}
assert options.mainDexKeepRules.isEmpty();
@@ -270,13 +270,12 @@
return true;
}
- private int validateOrComputeMinApiLevel(int computedMinApiLevel, DexReader dexReader) {
+ private AndroidApiLevel validateOrComputeMinApiLevel(
+ AndroidApiLevel computedMinApiLevel, DexReader dexReader) {
DexVersion version = dexReader.getDexVersion();
- if (options.minApiLevel == AndroidApiLevel.getDefault().getLevel()) {
- computedMinApiLevel = Math
- .max(computedMinApiLevel, AndroidApiLevel.getMinAndroidApiLevel(version).getLevel());
- } else if (!version
- .matchesApiLevel(AndroidApiLevel.getAndroidApiLevel(options.minApiLevel))) {
+ if (options.minApiLevel == AndroidApiLevel.getDefault()) {
+ computedMinApiLevel = computedMinApiLevel.max(AndroidApiLevel.getMinAndroidApiLevel(version));
+ } else if (!version.matchesApiLevel(options.minApiLevel)) {
throw new CompilationError("Dex file with version '" + version.getIntValue() +
"' cannot be used with min sdk level '" + options.minApiLevel + "'.");
}
@@ -340,7 +339,7 @@
}
hasReadProgramResourceFromDex = true;
List<DexParser<DexProgramClass>> dexParsers = new ArrayList<>(dexSources.size());
- int computedMinApiLevel = options.minApiLevel;
+ AndroidApiLevel computedMinApiLevel = options.minApiLevel;
for (ProgramResource input : dexSources) {
DexReader dexReader = new DexReader(input);
if (options.passthroughDexCode) {
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 42bd66b..dc93c37 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -318,7 +318,7 @@
return true;
}
- int apiLevel = options.minApiLevel;
+ AndroidApiLevel apiLevel = options.minApiLevel;
for (DexField field : mapping.getFields()) {
assert field.name.isValidSimpleName(apiLevel);
}
@@ -815,8 +815,7 @@
dest.putBytes(
options.testing.forceDexVersionBytes != null
? options.testing.forceDexVersionBytes
- : DexVersion.getDexVersion(AndroidApiLevel.getAndroidApiLevel(options.minApiLevel))
- .getBytes());
+ : DexVersion.getDexVersion(options.minApiLevel).getBytes());
dest.putByte(Constants.DEX_FILE_MAGIC_SUFFIX);
// Leave out checksum and signature for now.
dest.moveTo(Constants.FILE_SIZE_OFFSET);
@@ -1099,7 +1098,7 @@
private final Map<DexProgramClass, DexAnnotationDirectory> clazzToAnnotationDirectory
= new HashMap<>();
- private final int minApiLevel;
+ private final AndroidApiLevel minApiLevel;
private static <T> Object2IntMap<T> createObject2IntMap() {
Object2IntMap<T> result = new Object2IntLinkedOpenHashMap<>();
@@ -1148,7 +1147,7 @@
public boolean add(DexAnnotationSet annotationSet) {
// Until we fully drop support for API levels < 17, we have to emit an empty annotation set to
// work around a DALVIK bug. See b/36951668.
- if ((minApiLevel >= AndroidApiLevel.J_MR1.getLevel()) && annotationSet.isEmpty()) {
+ if ((minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.J_MR1)) && annotationSet.isEmpty()) {
return false;
}
return add(annotationSets, annotationSet);
@@ -1300,7 +1299,7 @@
public int getOffsetFor(DexAnnotationSet annotationSet) {
// Until we fully drop support for API levels < 17, we have to emit an empty annotation set to
// work around a DALVIK bug. See b/36951668.
- if ((minApiLevel >= AndroidApiLevel.J_MR1.getLevel()) && annotationSet.isEmpty()) {
+ if ((minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.J_MR1)) && annotationSet.isEmpty()) {
return 0;
}
return lookup(annotationSet, annotationSets);
@@ -1351,7 +1350,7 @@
void setOffsetFor(DexAnnotationSet annotationSet, int offset) {
// Until we fully drop support for API levels < 17, we have to emit an empty annotation set to
// work around a DALVIK bug. See b/36951668.
- assert (minApiLevel < AndroidApiLevel.J_MR1.getLevel()) || !annotationSet.isEmpty();
+ assert (minApiLevel.isLessThan(AndroidApiLevel.J_MR1)) || !annotationSet.isEmpty();
setOffsetFor(annotationSet, offset, annotationSets);
}
diff --git a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
index f8c4f3b..616fcf5 100644
--- a/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
+++ b/src/main/java/com/android/tools/r8/graph/AbstractAccessContexts.java
@@ -308,7 +308,9 @@
newAccessesWithContexts.computeIfAbsent(
lens.lookupField(access), ignore -> ProgramMethodSet.create());
for (ProgramMethod context : contexts) {
- newContexts.add(lens.mapProgramMethod(context, definitions));
+ ProgramMethod newContext = lens.mapProgramMethod(context, definitions);
+ assert newContext != null : "Unable to map context: " + context.toSourceString();
+ newContexts.add(newContext);
}
});
return new ConcreteAccessContexts(newAccessesWithContexts);
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index bf21997..4ef90be 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -28,7 +28,6 @@
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer;
import com.android.tools.r8.ir.optimize.library.LibraryMethodSideEffectModelCollection;
-import com.android.tools.r8.optimize.MemberRebindingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.LibraryModeledPredicate;
@@ -688,13 +687,26 @@
}
// Insert a member rebinding lens above the first unapplied lens.
- MemberRebindingLens appliedMemberRebindingLens =
- firstUnappliedLens.findPrevious(GraphLens::isMemberRebindingLens);
- GraphLens newMemberRebindingLens =
- appliedMemberRebindingLens != null
- ? appliedMemberRebindingLens.toRewrittenFieldRebindingLens(
- appView.dexItemFactory(), appliedLens)
- : GraphLens.getIdentityLens();
+ // TODO(b/182129249): Once the member rebinding phase has been removed, the MemberRebindingLens
+ // should be removed and all uses of FieldRebindingIdentityLens should be replaced by
+ // MemberRebindingIdentityLens.
+ NonIdentityGraphLens appliedMemberRebindingLens =
+ firstUnappliedLens.findPrevious(
+ previous ->
+ previous.isMemberRebindingLens() || previous.isMemberRebindingIdentityLens());
+ GraphLens newMemberRebindingLens;
+ if (appliedMemberRebindingLens != null) {
+ newMemberRebindingLens =
+ appliedMemberRebindingLens.isMemberRebindingLens()
+ ? appliedMemberRebindingLens
+ .asMemberRebindingLens()
+ .toRewrittenFieldRebindingLens(appView, appliedLens)
+ : appliedMemberRebindingLens
+ .asMemberRebindingIdentityLens()
+ .toRewrittenMemberRebindingIdentityLens(appView, appliedLens);
+ } else {
+ newMemberRebindingLens = GraphLens.getIdentityLens();
+ }
firstUnappliedLens.withAlternativeParentLens(
newMemberRebindingLens,
diff --git a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
index 1c7bf88..685188e 100644
--- a/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
+++ b/src/main/java/com/android/tools/r8/graph/BottomUpClassHierarchyTraversal.java
@@ -4,17 +4,19 @@
package com.android.tools.r8.graph;
+import java.util.function.Function;
+
public class BottomUpClassHierarchyTraversal<T extends DexClass>
extends ClassHierarchyTraversal<T, BottomUpClassHierarchyTraversal<T>> {
- private final SubtypingInfo subtypingInfo;
+ private final Function<DexType, Iterable<DexType>> immediateSubtypesProvider;
private BottomUpClassHierarchyTraversal(
AppView<? extends AppInfoWithClassHierarchy> appView,
- SubtypingInfo subtypingInfo,
+ Function<DexType, Iterable<DexType>> immediateSubtypesProvider,
Scope scope) {
super(appView, scope);
- this.subtypingInfo = subtypingInfo;
+ this.immediateSubtypesProvider = immediateSubtypesProvider;
}
/**
@@ -23,7 +25,8 @@
*/
public static BottomUpClassHierarchyTraversal<DexClass> forAllClasses(
AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
- return new BottomUpClassHierarchyTraversal<>(appView, subtypingInfo, Scope.ALL_CLASSES);
+ return new BottomUpClassHierarchyTraversal<>(
+ appView, subtypingInfo::allImmediateSubtypes, Scope.ALL_CLASSES);
}
/**
@@ -32,8 +35,18 @@
*/
public static BottomUpClassHierarchyTraversal<DexProgramClass> forProgramClasses(
AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) {
+ return forProgramClasses(appView, subtypingInfo::allImmediateSubtypes);
+ }
+
+ /**
+ * Returns a visitor that can be used to visit all the program classes that are reachable from a
+ * given set of sources.
+ */
+ public static BottomUpClassHierarchyTraversal<DexProgramClass> forProgramClasses(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ Function<DexType, Iterable<DexType>> immediateSubtypesProvider) {
return new BottomUpClassHierarchyTraversal<>(
- appView, subtypingInfo, Scope.ONLY_PROGRAM_CLASSES);
+ appView, immediateSubtypesProvider, Scope.ONLY_PROGRAM_CLASSES);
}
@Override
@@ -57,7 +70,7 @@
worklist.addFirst(clazzWithTypeT);
// Add subtypes to worklist.
- for (DexType subtype : subtypingInfo.allImmediateSubtypes(clazz.type)) {
+ for (DexType subtype : immediateSubtypesProvider.apply(clazz.getType())) {
DexClass definition = appView.definitionFor(subtype);
if (definition != null) {
if (scope != Scope.ONLY_PROGRAM_CLASSES || definition.isProgramClass()) {
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 31774f0..14f0015 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -32,7 +32,6 @@
import com.android.tools.r8.ir.conversion.CfSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -314,7 +313,7 @@
LensCodeRewriterUtils rewriter,
MethodVisitor visitor) {
GraphLens graphLens = appView.graphLens();
- assert verifyFrames(method.getDefinition(), appView, null, false).isValid()
+ assert verifyFrames(method.getDefinition(), appView, null).isValid()
: "Could not validate stack map frames";
DexItemFactory dexItemFactory = appView.dexItemFactory();
InitClassLens initClassLens = appView.initClassLens();
@@ -416,7 +415,8 @@
@Override
public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
- verifyFramesOrRemove(method.getDefinition(), appView, origin, true);
+ verifyFramesOrRemove(
+ method.getDefinition(), appView, origin, IRBuilder.lookupPrototypeChanges(appView, method));
return internalBuildPossiblyWithLocals(method, method, appView, null, null, origin, null);
}
@@ -428,21 +428,21 @@
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
- MethodProcessor methodProcessor) {
+ RewrittenPrototypeDescription protoChanges) {
assert valueNumberGenerator != null;
assert callerPosition != null;
- verifyFramesOrRemove(
- method.getDefinition(), appView, origin, methodProcessor.shouldApplyCodeRewritings(method));
+ assert protoChanges != null;
+ verifyFramesOrRemove(method.getDefinition(), appView, origin, protoChanges);
return internalBuildPossiblyWithLocals(
- context, method, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
+ context, method, appView, valueNumberGenerator, callerPosition, origin, protoChanges);
}
private void verifyFramesOrRemove(
DexEncodedMethod method,
AppView<?> appView,
Origin origin,
- boolean shouldApplyCodeRewritings) {
- stackMapStatus = verifyFrames(method, appView, origin, shouldApplyCodeRewritings);
+ RewrittenPrototypeDescription protoChanges) {
+ stackMapStatus = verifyFrames(method, appView, origin, protoChanges);
if (!stackMapStatus.isValid()) {
ArrayList<CfInstruction> copy = new ArrayList<>(instructions);
copy.removeIf(CfInstruction::isFrame);
@@ -458,7 +458,7 @@
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
- MethodProcessor methodProcessor) {
+ RewrittenPrototypeDescription protoChanges) {
if (!method.getDefinition().keepLocals(appView.options())) {
return internalBuild(
Collections.emptyList(),
@@ -468,10 +468,10 @@
valueNumberGenerator,
callerPosition,
origin,
- methodProcessor);
+ protoChanges);
} else {
return internalBuildWithLocals(
- context, method, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
+ context, method, appView, valueNumberGenerator, callerPosition, origin, protoChanges);
}
}
@@ -483,7 +483,7 @@
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
- MethodProcessor methodProcessor) {
+ RewrittenPrototypeDescription protoChanges) {
try {
return internalBuild(
Collections.unmodifiableList(localVariables),
@@ -493,7 +493,7 @@
valueNumberGenerator,
callerPosition,
origin,
- methodProcessor);
+ protoChanges);
} catch (InvalidDebugInfoException e) {
appView.options().warningInvalidDebugInfo(method, origin, e);
return internalBuild(
@@ -504,7 +504,7 @@
valueNumberGenerator,
callerPosition,
origin,
- methodProcessor);
+ protoChanges);
}
}
@@ -517,7 +517,7 @@
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
- MethodProcessor methodProcessor) {
+ RewrittenPrototypeDescription protoChanges) {
CfSourceCode source =
new CfSourceCode(
this,
@@ -527,11 +527,15 @@
callerPosition,
origin,
appView);
- IRBuilder builder =
- methodProcessor == null
- ? IRBuilder.create(method, appView, source, origin)
- : IRBuilder.createForInlining(
- method, appView, source, origin, methodProcessor, valueNumberGenerator);
+ IRBuilder builder;
+ if (valueNumberGenerator == null) {
+ assert protoChanges == null;
+ builder = IRBuilder.create(method, appView, source, origin);
+ } else {
+ builder =
+ IRBuilder.createForInlining(
+ method, appView, source, origin, valueNumberGenerator, protoChanges);
+ }
return builder.build(context);
}
@@ -716,8 +720,15 @@
thisLocalInfo.index, debugLocalInfo, thisLocalInfo.start, thisLocalInfo.end));
}
+ public StackMapStatus verifyFrames(DexEncodedMethod method, AppView<?> appView, Origin origin) {
+ return verifyFrames(method, appView, origin, RewrittenPrototypeDescription.none());
+ }
+
public StackMapStatus verifyFrames(
- DexEncodedMethod method, AppView<?> appView, Origin origin, boolean applyProtoTypeChanges) {
+ DexEncodedMethod method,
+ AppView<?> appView,
+ Origin origin,
+ RewrittenPrototypeDescription protoChanges) {
if (!appView.options().canUseInputStackMaps()
|| appView.options().testing.disableStackMapVerification) {
return StackMapStatus.NOT_PRESENT;
@@ -786,14 +797,8 @@
}
DexType context = appView.graphLens().lookupType(method.getHolderType());
DexType returnType = appView.graphLens().lookupType(method.getReference().getReturnType());
- RewrittenPrototypeDescription rewrittenDescription = RewrittenPrototypeDescription.none();
- if (applyProtoTypeChanges) {
- rewrittenDescription =
- appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference());
- if (!rewrittenDescription.isEmpty()
- && rewrittenDescription.getRewrittenReturnInfo() != null) {
- returnType = rewrittenDescription.getRewrittenReturnInfo().getOldType();
- }
+ if (!protoChanges.isEmpty() && protoChanges.getRewrittenReturnInfo() != null) {
+ returnType = protoChanges.getRewrittenReturnInfo().getOldType();
}
CfFrameVerificationHelper builder =
new CfFrameVerificationHelper(
@@ -809,8 +814,7 @@
builder.checkFrameAndSet(stateMap.get(null));
} else if (shouldComputeInitialFrame()) {
builder.checkFrameAndSet(
- new CfFrame(
- computeInitialLocals(context, method, rewrittenDescription), new ArrayDeque<>()));
+ new CfFrame(computeInitialLocals(context, method, protoChanges), new ArrayDeque<>()));
}
for (int i = 0; i < instructions.size(); i++) {
CfInstruction instruction = instructions.get(i);
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index b0d84f9..0f6438f 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.Outliner.OutlineCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
@@ -25,7 +24,7 @@
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
- MethodProcessor methodProcessor) {
+ RewrittenPrototypeDescription protoChanges) {
throw new Unreachable("Unexpected attempt to build IR graph for inlining from: "
+ getClass().getCanonicalName());
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 58862d7..ccc7dbc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -48,6 +48,10 @@
return getReference().asMethodReference();
}
+ public DexMethodSignature getMethodSignature() {
+ return getReference().getSignature();
+ }
+
public DexType getParameter(int index) {
return getReference().getParameter(index);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 2e42ccf..e4f7973 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.ir.conversion.DexSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.StringUtils;
@@ -232,7 +231,7 @@
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
- MethodProcessor methodProcessor) {
+ RewrittenPrototypeDescription protoChanges) {
DexSourceCode source =
new DexSourceCode(
this,
@@ -240,7 +239,7 @@
appView.graphLens().getOriginalMethodSignature(method.getReference()),
callerPosition);
return IRBuilder.createForInlining(
- method, appView, source, origin, methodProcessor, valueNumberGenerator)
+ method, appView, source, origin, valueNumberGenerator, protoChanges)
.build(context);
}
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 04cf5cc..9051374 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -56,6 +56,7 @@
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.DefaultMethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.ir.optimize.info.UpdatableMethodOptimizationInfo;
@@ -98,6 +99,8 @@
public class DexEncodedMethod extends DexEncodedMember<DexEncodedMethod, DexMethod>
implements StructuralItem<DexEncodedMethod> {
+ public static final boolean D8_R8_SYNTHESIZED = true;
+
public static final String CONFIGURATION_DEBUGGING_PREFIX = "Shaking error: Missing method in ";
/**
@@ -1318,6 +1321,7 @@
!isAbstract(),
builder ->
builder
+ .setGenericSignature(MethodTypeSignature.noSignature())
.setCode(
ForwardMethodBuilder.builder(definitions.dexItemFactory())
.setStaticSource(newMethod)
@@ -1379,7 +1383,8 @@
builder(this)
.promoteToStatic()
.withoutThisParameter()
- .adjustOptimizationInfoAfterRemovingThisParameter();
+ .adjustOptimizationInfoAfterRemovingThisParameter()
+ .setGenericSignature(MethodTypeSignature.noSignature());
DexEncodedMethod method = builder.build();
method.copyMetadata(this);
setObsolete();
@@ -1451,10 +1456,10 @@
public synchronized UpdatableMethodOptimizationInfo getMutableOptimizationInfo() {
checkIfObsolete();
- if (optimizationInfo == DefaultMethodOptimizationInfo.DEFAULT_INSTANCE) {
- optimizationInfo = optimizationInfo.mutableCopy();
- }
- return (UpdatableMethodOptimizationInfo) optimizationInfo;
+ UpdatableMethodOptimizationInfo updatableMethodOptimizationInfo =
+ optimizationInfo.asUpdatableMethodOptimizationInfo();
+ this.optimizationInfo = updatableMethodOptimizationInfo;
+ return updatableMethodOptimizationInfo;
}
public void setOptimizationInfo(UpdatableMethodOptimizationInfo info) {
@@ -1462,6 +1467,11 @@
optimizationInfo = info;
}
+ public void setMinApiOptimizationInfo(DefaultMethodOptimizationWithMinApiInfo info) {
+ checkIfObsolete();
+ optimizationInfo = info;
+ }
+
public synchronized void abandonCallSiteOptimizationInfo() {
checkIfObsolete();
callSiteOptimizationInfo = CallSiteOptimizationInfo.abandoned();
@@ -1510,7 +1520,7 @@
private DexMethod method;
private MethodAccessFlags accessFlags;
- private final MethodTypeSignature genericSignature;
+ private MethodTypeSignature genericSignature;
private final DexAnnotationSet annotations;
private OptionalBool isLibraryMethodOverride = OptionalBool.UNKNOWN;
private ParameterAnnotationsList parameterAnnotations;
@@ -1710,5 +1720,10 @@
buildConsumer.accept(result);
return result;
}
+
+ public Builder setGenericSignature(MethodTypeSignature methodSignature) {
+ this.genericSignature = methodSignature;
+ return this;
+ }
}
}
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 7d95e87..646c9a0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -29,10 +29,12 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.LRUCacheTable;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.base.Strings;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
@@ -2498,6 +2500,22 @@
return createMethod(holder, proto, createString(name));
}
+ public DexMethod createMethod(MethodReference methodReference) {
+ DexString[] formals = new DexString[methodReference.getFormalTypes().size()];
+ ListUtils.forEachWithIndex(
+ methodReference.getFormalTypes(),
+ (formal, index) -> {
+ formals[index] = createString(formal.getDescriptor());
+ });
+ return createMethod(
+ createString(methodReference.getHolderClass().getDescriptor()),
+ createString(methodReference.getMethodName()),
+ methodReference.getReturnType() == null
+ ? voidDescriptor
+ : createString(methodReference.getReturnType().getDescriptor()),
+ formals);
+ }
+
public DexMethodHandle createMethodHandle(
MethodHandleType type,
DexMember<? extends DexItem, ? extends DexMember<?, ?>> fieldOrMethod,
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index c96dc4a..15ac70a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -292,6 +292,10 @@
return dexItemFactory.createMethod(reference.getContextType(), proto, name);
}
+ public DexMethod withName(String name, DexItemFactory dexItemFactory) {
+ return withName(dexItemFactory.createString(name), dexItemFactory);
+ }
+
public DexMethod withName(DexString name, DexItemFactory dexItemFactory) {
return dexItemFactory.createMethod(holder, proto, name);
}
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 ba4d653..87630f2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -399,9 +399,9 @@
}
}
- public static boolean isValidSimpleName(int apiLevel, String string) {
+ public static boolean isValidSimpleName(AndroidApiLevel apiLevel, String string) {
// space characters are not allowed prior to Android R
- if (apiLevel < AndroidApiLevel.R.getLevel()) {
+ if (apiLevel.isLessThan(AndroidApiLevel.R)) {
int cp;
for (int i = 0; i < string.length(); ) {
cp = string.codePointAt(i);
@@ -414,9 +414,9 @@
return true;
}
- public boolean isValidSimpleName(int apiLevel) {
+ public boolean isValidSimpleName(AndroidApiLevel apiLevel) {
// space characters are not allowed prior to Android R
- if (apiLevel < AndroidApiLevel.R.getLevel()) {
+ if (apiLevel.isLessThan(AndroidApiLevel.R)) {
try {
return isValidSimpleName(apiLevel, decode());
} catch (UTFDataFormatException e) {
diff --git a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
index fde5f6c..d2ea75e 100644
--- a/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/EnclosingMethodAttribute.java
@@ -52,6 +52,10 @@
return enclosingMethod != null;
}
+ public boolean hasEnclosingClass() {
+ return enclosingClass != null;
+ }
+
public DexMethod getEnclosingMethod() {
return enclosingMethod;
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 9a34638..419125d 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -173,6 +173,7 @@
this.name = name;
this.classBound = classBound;
this.interfaceBounds = interfaceBounds;
+ assert classBound != null;
assert interfaceBounds != null;
}
@@ -189,14 +190,16 @@
}
public FormalTypeParameter visit(GenericSignatureVisitor visitor) {
- FieldTypeSignature rewrittenClassBound =
- classBound == null ? null : visitor.visitClassBound(classBound);
+ FieldTypeSignature rewrittenClassBound = visitor.visitClassBound(classBound);
List<FieldTypeSignature> rewrittenInterfaceBounds =
visitor.visitInterfaceBounds(interfaceBounds);
if (classBound == rewrittenClassBound && interfaceBounds == rewrittenInterfaceBounds) {
return this;
}
- return new FormalTypeParameter(name, rewrittenClassBound, rewrittenInterfaceBounds);
+ return new FormalTypeParameter(
+ name,
+ rewrittenClassBound == null ? FieldTypeSignature.noSignature() : rewrittenClassBound,
+ rewrittenInterfaceBounds);
}
}
@@ -286,6 +289,10 @@
public static ClassSignature noSignature() {
return NO_CLASS_SIGNATURE;
}
+
+ public ClassSignature toObjectBoundWithSameFormals(ClassTypeSignature objectBound) {
+ return new ClassSignature(formalTypeParameters, objectBound, getEmptySuperInterfaces());
+ }
}
private static class InvalidClassSignature extends ClassSignature {
@@ -649,7 +656,7 @@
if (elementSignature == rewrittenElementSignature) {
return this;
}
- return new ArrayTypeSignature(elementSignature, getWildcardIndicator());
+ return new ArrayTypeSignature(rewrittenElementSignature, getWildcardIndicator());
}
}
@@ -891,7 +898,7 @@
return parser.parseClassSignature(signature);
} catch (GenericSignatureFormatError e) {
diagnosticsHandler.warning(
- GenericSignatureDiagnostic.invalidClassSignature(signature, className, origin, e));
+ GenericSignatureFormatDiagnostic.invalidClassSignature(signature, className, origin, e));
return ClassSignature.NO_CLASS_SIGNATURE;
}
}
@@ -910,7 +917,7 @@
return parser.parseFieldTypeSignature(signature);
} catch (GenericSignatureFormatError e) {
diagnosticsHandler.warning(
- GenericSignatureDiagnostic.invalidFieldSignature(signature, fieldName, origin, e));
+ GenericSignatureFormatDiagnostic.invalidFieldSignature(signature, fieldName, origin, e));
return GenericSignature.NO_FIELD_TYPE_SIGNATURE;
}
}
@@ -929,7 +936,8 @@
return parser.parseMethodTypeSignature(signature);
} catch (GenericSignatureFormatError e) {
diagnosticsHandler.warning(
- GenericSignatureDiagnostic.invalidMethodSignature(signature, methodName, origin, e));
+ GenericSignatureFormatDiagnostic.invalidMethodSignature(
+ signature, methodName, origin, e));
return MethodTypeSignature.NO_METHOD_TYPE_SIGNATURE;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
similarity index 65%
rename from src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
rename to src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
index 7c0ee29..1a05232 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureContextBuilder.java
@@ -4,11 +4,11 @@
package com.android.tools.r8.graph;
-import static com.android.tools.r8.graph.GenericSignatureTypeVariableRemover.TypeParameterContext.empty;
-import static com.google.common.base.Predicates.alwaysFalse;
+import static com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext.empty;
import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -18,9 +18,8 @@
import java.util.Set;
import java.util.function.Predicate;
-public class GenericSignatureTypeVariableRemover {
+public class GenericSignatureContextBuilder {
- private final DexType objectType;
private final Map<DexReference, TypeParameterSubstitutions> formalsInfo;
private final Map<DexReference, DexReference> enclosingInfo;
@@ -51,7 +50,7 @@
}
}
- static class TypeParameterContext {
+ public static class TypeParameterContext {
private static final TypeParameterContext EMPTY =
new TypeParameterContext(Collections.emptyMap(), Collections.emptySet());
@@ -69,37 +68,99 @@
if (information == null) {
return this;
}
- HashMap<String, DexType> newPruned = new HashMap<>(prunedParametersWithBounds);
- HashSet<String> newLiveParameters = new HashSet<>(liveParameters);
- information.parametersWithBounds.forEach(
- (param, type) -> {
- if (dead) {
- newPruned.put(param, type);
- newLiveParameters.remove(param);
- } else {
- newLiveParameters.add(param);
- newPruned.remove(param);
- }
- });
- return new TypeParameterContext(newPruned, newLiveParameters);
+ return dead
+ ? addPrunedSubstitutions(information.parametersWithBounds)
+ : addLiveParameters(information.parametersWithBounds.keySet());
}
public static TypeParameterContext empty() {
return EMPTY;
}
+
+ public boolean isLiveParameter(String parameterName) {
+ return liveParameters.contains(parameterName);
+ }
+
+ public DexType getPrunedSubstitution(String parameterName) {
+ assert !isLiveParameter(parameterName);
+ return prunedParametersWithBounds.get(parameterName);
+ }
+
+ public TypeParameterContext addLiveParameters(Collection<String> typeParameters) {
+ if (typeParameters.isEmpty()) {
+ return this;
+ }
+ HashSet<String> newLiveParameters = new HashSet<>();
+ newLiveParameters.addAll(liveParameters);
+ newLiveParameters.addAll(typeParameters);
+ HashMap<String, DexType> newPruned = new HashMap<>();
+ prunedParametersWithBounds.forEach(
+ (name, type) -> {
+ if (!typeParameters.contains(name)) {
+ newPruned.put(name, type);
+ }
+ });
+ return new TypeParameterContext(newPruned, newLiveParameters);
+ }
+
+ public TypeParameterContext addPrunedSubstitutions(Map<String, DexType> substitutions) {
+ if (substitutions.isEmpty()) {
+ return this;
+ }
+ HashMap<String, DexType> newPruned = new HashMap<>();
+ newPruned.putAll(prunedParametersWithBounds);
+ newPruned.putAll(substitutions);
+ HashSet<String> newLiveParameters = new HashSet<>();
+ liveParameters.forEach(
+ name -> {
+ if (!substitutions.containsKey(name)) {
+ newLiveParameters.add(name);
+ }
+ });
+ return new TypeParameterContext(newPruned, newLiveParameters);
+ }
}
- private GenericSignatureTypeVariableRemover(
+ public static class AlwaysLiveTypeParameterContext extends TypeParameterContext {
+
+ private AlwaysLiveTypeParameterContext() {
+ super(Collections.emptyMap(), Collections.emptySet());
+ }
+
+ public static AlwaysLiveTypeParameterContext create() {
+ return new AlwaysLiveTypeParameterContext();
+ }
+
+ @Override
+ public boolean isLiveParameter(String parameterName) {
+ return true;
+ }
+
+ @Override
+ public DexType getPrunedSubstitution(String parameterName) {
+ assert false;
+ return null;
+ }
+
+ @Override
+ public TypeParameterContext addLiveParameters(Collection<String> typeParameters) {
+ return this;
+ }
+
+ @Override
+ public TypeParameterContext addPrunedSubstitutions(Map<String, DexType> substitutions) {
+ return this;
+ }
+ }
+
+ private GenericSignatureContextBuilder(
Map<DexReference, TypeParameterSubstitutions> formalsInfo,
- Map<DexReference, DexReference> enclosingInfo,
- DexType objectType) {
+ Map<DexReference, DexReference> enclosingInfo) {
this.formalsInfo = formalsInfo;
this.enclosingInfo = enclosingInfo;
- this.objectType = objectType;
}
- public static GenericSignatureTypeVariableRemover create(
- AppView<?> appView, List<DexProgramClass> programClasses) {
+ public static GenericSignatureContextBuilder create(List<DexProgramClass> programClasses) {
Map<DexReference, TypeParameterSubstitutions> formalsInfo = new IdentityHashMap<>();
Map<DexReference, DexReference> enclosingInfo = new IdentityHashMap<>();
programClasses.forEach(
@@ -137,8 +198,13 @@
: enclosingMethodAttribute.getEnclosingClass());
}
});
- return new GenericSignatureTypeVariableRemover(
- formalsInfo, enclosingInfo, appView.dexItemFactory().objectType);
+ return new GenericSignatureContextBuilder(formalsInfo, enclosingInfo);
+ }
+
+ public TypeParameterContext computeTypeParameterContext(
+ AppView<?> appView, DexReference reference, Predicate<DexType> wasPruned) {
+ assert !wasPruned.test(reference.getContextType());
+ return computeTypeParameterContext(appView, reference, wasPruned, false);
}
private TypeParameterContext computeTypeParameterContext(
@@ -175,7 +241,7 @@
return typeParameterContext.combine(formalsInfo.get(reference), prunedHere);
}
- private static boolean hasPrunedRelationship(
+ public boolean hasPrunedRelationship(
AppView<?> appView,
DexReference enclosingReference,
DexType enclosedClassType,
@@ -189,8 +255,17 @@
if (wasPruned.test(enclosingReference.getContextType()) || wasPruned.test(enclosedClassType)) {
return true;
}
- DexClass enclosingClass = appView.definitionFor(enclosingReference.getContextType());
- DexClass enclosedClass = appView.definitionFor(enclosedClassType);
+ // TODO(b/187035453): We should visit generic signatures in the enqueuer.
+ DexClass enclosingClass =
+ appView
+ .appInfo()
+ .definitionForWithoutExistenceAssert(
+ appView.graphLens().lookupClassType(enclosingReference.getContextType()));
+ DexClass enclosedClass =
+ appView
+ .appInfo()
+ .definitionForWithoutExistenceAssert(
+ appView.graphLens().lookupClassType(enclosedClassType));
if (enclosingClass == null || enclosedClass == null) {
return true;
}
@@ -207,68 +282,15 @@
}
}
- private static boolean hasGenericTypeVariables(
+ public boolean hasGenericTypeVariables(
AppView<?> appView, DexType type, Predicate<DexType> wasPruned) {
if (wasPruned.test(type)) {
return false;
}
- DexClass clazz = appView.definitionFor(type);
+ DexClass clazz = appView.definitionFor(appView.graphLens().lookupClassType(type));
if (clazz == null || clazz.isNotProgramClass() || clazz.getClassSignature().isInvalid()) {
return true;
}
return !clazz.getClassSignature().getFormalTypeParameters().isEmpty();
}
-
- public void removeDeadGenericSignatureTypeVariables(AppView<?> appView) {
- Predicate<DexType> wasPruned =
- appView.hasLiveness() ? appView.withLiveness().appInfo()::wasPruned : alwaysFalse();
- GenericSignaturePartialTypeArgumentApplier baseArgumentApplier =
- GenericSignaturePartialTypeArgumentApplier.build(
- objectType,
- (enclosing, enclosed) -> hasPrunedRelationship(appView, enclosing, enclosed, wasPruned),
- type -> hasGenericTypeVariables(appView, type, wasPruned));
- appView
- .appInfo()
- .classes()
- .forEach(
- clazz -> {
- if (clazz.getClassSignature().isInvalid()) {
- return;
- }
- TypeParameterContext computedClassFormals =
- computeTypeParameterContext(appView, clazz.getType(), wasPruned, false);
- GenericSignaturePartialTypeArgumentApplier classArgumentApplier =
- baseArgumentApplier.addSubstitutionsAndVariables(
- computedClassFormals.prunedParametersWithBounds,
- computedClassFormals.liveParameters);
- clazz.setClassSignature(
- classArgumentApplier.visitClassSignature(clazz.getClassSignature()));
- clazz
- .methods()
- .forEach(
- method -> {
- MethodTypeSignature methodSignature = method.getGenericSignature();
- if (methodSignature.hasSignature()
- && method.getGenericSignature().isValid()) {
- // The reflection api do not distinguish static methods context from
- // virtual methods.
- method.setGenericSignature(
- classArgumentApplier
- .buildForMethod(methodSignature.getFormalTypeParameters())
- .visitMethodSignature(methodSignature));
- }
- });
- clazz
- .instanceFields()
- .forEach(
- field -> {
- if (field.getGenericSignature().hasSignature()
- && field.getGenericSignature().isValid()) {
- field.setGenericSignature(
- classArgumentApplier.visitFieldTypeSignature(
- field.getGenericSignature()));
- }
- });
- });
- }
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
index 3401560..7dcfb37 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureCorrectnessHelper.java
@@ -9,7 +9,9 @@
import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_SUPER_TYPE;
import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.INVALID_TYPE_VARIABLE_UNDEFINED;
import static com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult.VALID;
+import static com.google.common.base.Predicates.alwaysFalse;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.GenericSignature.ClassSignature;
import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
import com.android.tools.r8.graph.GenericSignature.DexDefinitionSignature;
@@ -18,9 +20,9 @@
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GenericSignature.ReturnType;
import com.android.tools.r8.graph.GenericSignature.TypeSignature;
-import java.util.HashSet;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext;
+import com.android.tools.r8.utils.ListUtils;
import java.util.List;
-import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -29,90 +31,163 @@
private enum Mode {
VERIFY,
- MARK_AS_INVALID;
+ CLEAR_IF_INVALID;
public boolean doNotVerify() {
- return markAsInvalid();
+ return clearIfInvalid();
}
- public boolean markAsInvalid() {
- return this == MARK_AS_INVALID;
+ public boolean clearIfInvalid() {
+ return this == CLEAR_IF_INVALID;
}
}
public enum SignatureEvaluationResult {
INVALID_SUPER_TYPE,
+ INVALID_INTERFACE_TYPE,
INVALID_INTERFACE_COUNT,
INVALID_APPLICATION_COUNT,
INVALID_TYPE_VARIABLE_UNDEFINED,
VALID;
- boolean isValid() {
+ public boolean isValid() {
return this == VALID;
}
- boolean isInvalid() {
+ public boolean isInvalid() {
return this != VALID;
}
+
+ public SignatureEvaluationResult combine(SignatureEvaluationResult other) {
+ return isInvalid() ? this : other;
+ }
+
+ public String getDescription() {
+ switch (this) {
+ case INVALID_APPLICATION_COUNT:
+ return "The applied generic arguments have different count than the expected formals";
+ case INVALID_INTERFACE_COUNT:
+ return "The generic signature has a different number of interfaces than the class";
+ case INVALID_SUPER_TYPE:
+ return "The generic super type is not the same as the class super type";
+ case INVALID_TYPE_VARIABLE_UNDEFINED:
+ return "A type variable is not in scope";
+ default:
+ assert this.isValid();
+ throw new Unreachable("Should not throw an error for a valid signature");
+ }
+ }
}
private final AppView<?> appView;
private final Mode mode;
+ private final GenericSignatureContextBuilder contextBuilder;
- private GenericSignatureCorrectnessHelper(AppView<?> appView, Mode mode) {
+ private GenericSignatureCorrectnessHelper(
+ AppView<?> appView, GenericSignatureContextBuilder contextBuilder, Mode mode) {
this.appView = appView;
+ this.contextBuilder = contextBuilder;
this.mode = mode;
}
- public static GenericSignatureCorrectnessHelper createForInitialCheck(AppView<?> appView) {
- return new GenericSignatureCorrectnessHelper(appView, Mode.MARK_AS_INVALID);
+ public static GenericSignatureCorrectnessHelper createForInitialCheck(
+ AppView<?> appView, GenericSignatureContextBuilder contextBuilder) {
+ return new GenericSignatureCorrectnessHelper(appView, contextBuilder, Mode.CLEAR_IF_INVALID);
}
- public static GenericSignatureCorrectnessHelper createForVerification(AppView<?> appView) {
- return new GenericSignatureCorrectnessHelper(appView, Mode.VERIFY);
+ public static GenericSignatureCorrectnessHelper createForVerification(
+ AppView<?> appView, GenericSignatureContextBuilder contextBuilder) {
+ return new GenericSignatureCorrectnessHelper(appView, contextBuilder, Mode.VERIFY);
}
- public void run() {
- appView.appInfo().classes().forEach(this::evaluateSignaturesForClass);
+ public SignatureEvaluationResult run(List<DexProgramClass> programClasses) {
+ if (appView.options().disableGenericSignatureValidation) {
+ return VALID;
+ }
+ for (DexProgramClass clazz : programClasses) {
+ SignatureEvaluationResult evaluationResult = evaluateSignaturesForClass(clazz);
+ if (evaluationResult.isInvalid()) {
+ return evaluationResult;
+ }
+ }
+ return VALID;
}
public SignatureEvaluationResult evaluateSignaturesForClass(DexProgramClass clazz) {
+ if (appView.options().disableGenericSignatureValidation) {
+ return VALID;
+ }
+
+ TypeParameterContext typeParameterContext =
+ contextBuilder.computeTypeParameterContext(appView, clazz.type, alwaysFalse());
+
GenericSignatureContextEvaluator genericSignatureContextEvaluator =
- new GenericSignatureContextEvaluator(appView, clazz, mode);
- ClassSignature classSignature = clazz.getClassSignature();
- SignatureEvaluationResult result = VALID;
- if (classSignature.hasNoSignature() || !classSignature.isInvalid()) {
- result = genericSignatureContextEvaluator.evaluateClassSignature(classSignature);
- if (result.isInvalid() && mode.markAsInvalid()) {
- clazz.setClassSignature(classSignature.toInvalid());
- }
+ new GenericSignatureContextEvaluator(appView, mode, clazz);
+
+ SignatureEvaluationResult result =
+ genericSignatureContextEvaluator.evaluateClassSignatureForContext(typeParameterContext);
+ if (result.isInvalid() && mode.clearIfInvalid()) {
+ appView
+ .options()
+ .reporter
+ .info(
+ GenericSignatureValidationDiagnostic.invalidClassSignature(
+ clazz.getClassSignature().toString(),
+ clazz.getTypeName(),
+ clazz.getOrigin(),
+ result));
+ clazz.clearClassSignature();
}
for (DexEncodedMethod method : clazz.methods()) {
- SignatureEvaluationResult methodResult =
- evaluate(
- method::getGenericSignature,
- genericSignatureContextEvaluator::visitMethodSignature,
- method::setGenericSignature);
- if (result.isValid() && methodResult.isInvalid()) {
- result = methodResult;
- }
+ result =
+ result.combine(
+ evaluate(
+ method::getGenericSignature,
+ methodSignature ->
+ genericSignatureContextEvaluator.visitMethodSignature(
+ methodSignature, typeParameterContext),
+ invalidResult -> {
+ appView
+ .options()
+ .reporter
+ .info(
+ GenericSignatureValidationDiagnostic.invalidMethodSignature(
+ method.getGenericSignature().toString(),
+ method.toSourceString(),
+ clazz.getOrigin(),
+ invalidResult));
+ method.clearGenericSignature();
+ }));
}
for (DexEncodedField field : clazz.fields()) {
- SignatureEvaluationResult fieldResult =
- evaluate(
- field::getGenericSignature,
- genericSignatureContextEvaluator::visitFieldTypeSignature,
- field::setGenericSignature);
- if (result.isValid() && fieldResult.isInvalid()) {
- result = fieldResult;
- }
+ result =
+ result.combine(
+ evaluate(
+ field::getGenericSignature,
+ fieldSignature ->
+ genericSignatureContextEvaluator.visitFieldTypeSignature(
+ fieldSignature, typeParameterContext),
+ invalidResult -> {
+ appView
+ .options()
+ .reporter
+ .info(
+ GenericSignatureValidationDiagnostic.invalidFieldSignature(
+ field.getGenericSignature().toString(),
+ field.toSourceString(),
+ clazz.getOrigin(),
+ invalidResult));
+ field.clearGenericSignature();
+ }));
}
return result;
}
@SuppressWarnings("unchecked")
private <T extends DexDefinitionSignature<?>> SignatureEvaluationResult evaluate(
- Supplier<T> getter, Function<T, SignatureEvaluationResult> evaluate, Consumer<T> setter) {
+ Supplier<T> getter,
+ Function<T, SignatureEvaluationResult> evaluate,
+ Consumer<SignatureEvaluationResult> invalidAction) {
T signature = getter.get();
if (signature.hasNoSignature() || signature.isInvalid()) {
// Already marked as invalid, do nothing
@@ -120,8 +195,8 @@
}
SignatureEvaluationResult signatureResult = evaluate.apply(signature);
assert signatureResult.isValid() || mode.doNotVerify();
- if (signatureResult.isInvalid() && mode.doNotVerify()) {
- setter.accept((T) signature.toInvalid());
+ if (signatureResult.isInvalid() && mode.clearIfInvalid()) {
+ invalidAction.accept(signatureResult);
}
return signatureResult;
}
@@ -130,39 +205,39 @@
private final AppView<?> appView;
private final DexProgramClass context;
- private final Set<String> classFormalTypeParameters = new HashSet<>();
- private final Set<String> methodTypeArguments = new HashSet<>();
private final Mode mode;
- public GenericSignatureContextEvaluator(
- AppView<?> appView, DexProgramClass context, Mode mode) {
+ private GenericSignatureContextEvaluator(
+ AppView<?> appView, Mode mode, DexProgramClass context) {
this.appView = appView;
- this.context = context;
this.mode = mode;
+ this.context = context;
}
- private SignatureEvaluationResult evaluateClassSignature(ClassSignature classSignature) {
- classSignature
- .getFormalTypeParameters()
- .forEach(param -> classFormalTypeParameters.add(param.name));
- if (classSignature.hasNoSignature()) {
+ private SignatureEvaluationResult evaluateClassSignatureForContext(
+ TypeParameterContext typeParameterContext) {
+ ClassSignature classSignature = context.classSignature;
+ if (classSignature.hasNoSignature() || classSignature.isInvalid()) {
return VALID;
}
SignatureEvaluationResult signatureEvaluationResult =
- evaluateFormalTypeParameters(classSignature.formalTypeParameters);
+ evaluateFormalTypeParameters(classSignature.formalTypeParameters, typeParameterContext);
if (signatureEvaluationResult.isInvalid()) {
return signatureEvaluationResult;
}
- if ((context.superType != appView.dexItemFactory().objectType
- && context.superType != classSignature.superClassSignature().type())
- || (context.superType == appView.dexItemFactory().objectType
- && classSignature.superClassSignature().hasNoSignature())) {
+ if (context.superType == appView.dexItemFactory().objectType
+ && classSignature.superClassSignature().hasNoSignature()) {
+ // We represent no signature as object.
+ } else if (context.superType
+ != appView.graphLens().lookupClassType(classSignature.superClassSignature().type())) {
assert mode.doNotVerify();
return INVALID_SUPER_TYPE;
}
signatureEvaluationResult =
evaluateTypeArgumentsAppliedToType(
- classSignature.superClassSignature().typeArguments(), context.superType);
+ classSignature.superClassSignature().typeArguments(),
+ context.superType,
+ typeParameterContext);
if (signatureEvaluationResult.isInvalid()) {
return signatureEvaluationResult;
}
@@ -175,7 +250,7 @@
for (int i = 0; i < actualInterfaces.length; i++) {
signatureEvaluationResult =
evaluateTypeArgumentsAppliedToType(
- superInterfaces.get(i).typeArguments(), actualInterfaces[i]);
+ superInterfaces.get(i).typeArguments(), actualInterfaces[i], typeParameterContext);
if (signatureEvaluationResult.isInvalid()) {
return signatureEvaluationResult;
}
@@ -183,37 +258,46 @@
return VALID;
}
- private SignatureEvaluationResult visitMethodSignature(MethodTypeSignature methodSignature) {
- methodSignature
- .getFormalTypeParameters()
- .forEach(param -> methodTypeArguments.add(param.name));
+ private SignatureEvaluationResult visitMethodSignature(
+ MethodTypeSignature methodSignature, TypeParameterContext typeParameterContext) {
+ // If the class context is invalid, we cannot reason about the method signatures.
+ if (context.classSignature.isInvalid()) {
+ return VALID;
+ }
+ TypeParameterContext methodContext =
+ methodSignature.formalTypeParameters.isEmpty()
+ ? typeParameterContext
+ : typeParameterContext.addLiveParameters(
+ ListUtils.map(
+ methodSignature.getFormalTypeParameters(), FormalTypeParameter::getName));
SignatureEvaluationResult evaluateResult =
- evaluateFormalTypeParameters(methodSignature.getFormalTypeParameters());
+ evaluateFormalTypeParameters(methodSignature.getFormalTypeParameters(), methodContext);
if (evaluateResult.isInvalid()) {
return evaluateResult;
}
- evaluateResult = evaluateTypeArguments(methodSignature.typeSignatures);
+ evaluateResult = evaluateTypeArguments(methodSignature.typeSignatures, methodContext);
if (evaluateResult.isInvalid()) {
return evaluateResult;
}
- evaluateResult = evaluateTypeArguments(methodSignature.throwsSignatures);
+ evaluateResult = evaluateTypeArguments(methodSignature.throwsSignatures, methodContext);
if (evaluateResult.isInvalid()) {
return evaluateResult;
}
ReturnType returnType = methodSignature.returnType();
if (!returnType.isVoidDescriptor()) {
- evaluateResult = evaluateTypeArgument(returnType.typeSignature());
+ evaluateResult = evaluateTypeArgument(returnType.typeSignature(), methodContext);
if (evaluateResult.isInvalid()) {
return evaluateResult;
}
}
- methodTypeArguments.clear();
return evaluateResult;
}
- private SignatureEvaluationResult evaluateTypeArguments(List<TypeSignature> typeSignatures) {
+ private SignatureEvaluationResult evaluateTypeArguments(
+ List<TypeSignature> typeSignatures, TypeParameterContext typeParameterContext) {
for (TypeSignature typeSignature : typeSignatures) {
- SignatureEvaluationResult signatureEvaluationResult = evaluateTypeArgument(typeSignature);
+ SignatureEvaluationResult signatureEvaluationResult =
+ evaluateTypeArgument(typeSignature, typeParameterContext);
if (signatureEvaluationResult.isInvalid()) {
return signatureEvaluationResult;
}
@@ -221,14 +305,20 @@
return VALID;
}
- private SignatureEvaluationResult visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
- return evaluateTypeArgument(fieldSignature);
+ private SignatureEvaluationResult visitFieldTypeSignature(
+ FieldTypeSignature fieldSignature, TypeParameterContext typeParameterContext) {
+ // If the class context is invalid, we cannot reason about the method signatures.
+ if (context.classSignature.isInvalid()) {
+ return VALID;
+ }
+ return evaluateTypeArgument(fieldSignature, typeParameterContext);
}
private SignatureEvaluationResult evaluateFormalTypeParameters(
- List<FormalTypeParameter> typeParameters) {
+ List<FormalTypeParameter> typeParameters, TypeParameterContext typeParameterContext) {
for (FormalTypeParameter typeParameter : typeParameters) {
- SignatureEvaluationResult evaluationResult = evaluateTypeParameter(typeParameter);
+ SignatureEvaluationResult evaluationResult =
+ evaluateTypeParameter(typeParameter, typeParameterContext);
if (evaluationResult.isInvalid()) {
return evaluationResult;
}
@@ -236,66 +326,79 @@
return VALID;
}
- private SignatureEvaluationResult evaluateTypeParameter(FormalTypeParameter typeParameter) {
- SignatureEvaluationResult evaluationResult = evaluateTypeArgument(typeParameter.classBound);
+ private SignatureEvaluationResult evaluateTypeParameter(
+ FormalTypeParameter typeParameter, TypeParameterContext typeParameterContext) {
+ SignatureEvaluationResult evaluationResult =
+ evaluateTypeArgument(typeParameter.classBound, typeParameterContext);
if (evaluationResult.isInvalid()) {
return evaluationResult;
}
- if (typeParameter.interfaceBounds != null) {
- for (FieldTypeSignature interfaceBound : typeParameter.interfaceBounds) {
- evaluationResult = evaluateTypeArgument(interfaceBound);
- if (evaluationResult != VALID) {
- return evaluationResult;
- }
+ for (FieldTypeSignature interfaceBound : typeParameter.interfaceBounds) {
+ evaluationResult = evaluateTypeArgument(interfaceBound, typeParameterContext);
+ if (evaluationResult != VALID) {
+ return evaluationResult;
}
}
return VALID;
}
- private SignatureEvaluationResult evaluateTypeArgument(TypeSignature typeSignature) {
+ private SignatureEvaluationResult evaluateTypeArgument(
+ TypeSignature typeSignature, TypeParameterContext typeParameterContext) {
if (typeSignature.isBaseTypeSignature()) {
return VALID;
}
FieldTypeSignature fieldTypeSignature = typeSignature.asFieldTypeSignature();
- if (fieldTypeSignature.hasNoSignature()) {
+ if (fieldTypeSignature.hasNoSignature() || fieldTypeSignature.isStar()) {
return VALID;
}
if (fieldTypeSignature.isTypeVariableSignature()) {
// This is in an applied position, just check that the variable is registered.
String typeVariable = fieldTypeSignature.asTypeVariableSignature().typeVariable();
- if (classFormalTypeParameters.contains(typeVariable)
- || methodTypeArguments.contains(typeVariable)) {
+ if (typeParameterContext.isLiveParameter(typeVariable)) {
return VALID;
}
assert mode.doNotVerify();
return INVALID_TYPE_VARIABLE_UNDEFINED;
}
if (fieldTypeSignature.isArrayTypeSignature()) {
- return evaluateTypeArgument(fieldTypeSignature.asArrayTypeSignature().elementSignature());
+ return evaluateTypeArgument(
+ fieldTypeSignature.asArrayTypeSignature().elementSignature(), typeParameterContext);
}
assert fieldTypeSignature.isClassTypeSignature();
- return evaluateTypeArguments(fieldTypeSignature.asClassTypeSignature());
+ return evaluateTypeArguments(fieldTypeSignature.asClassTypeSignature(), typeParameterContext);
}
- private SignatureEvaluationResult evaluateTypeArguments(ClassTypeSignature classTypeSignature) {
+ private SignatureEvaluationResult evaluateTypeArguments(
+ ClassTypeSignature classTypeSignature, TypeParameterContext typeParameterContext) {
return evaluateTypeArgumentsAppliedToType(
- classTypeSignature.typeArguments, classTypeSignature.type());
+ classTypeSignature.typeArguments, classTypeSignature.type(), typeParameterContext);
}
private SignatureEvaluationResult evaluateTypeArgumentsAppliedToType(
- List<FieldTypeSignature> typeArguments, DexType type) {
+ List<FieldTypeSignature> typeArguments,
+ DexType type,
+ TypeParameterContext typeParameterContext) {
for (FieldTypeSignature typeArgument : typeArguments) {
- SignatureEvaluationResult evaluationResult = evaluateTypeArgument(typeArgument);
+ SignatureEvaluationResult evaluationResult =
+ evaluateTypeArgument(typeArgument, typeParameterContext);
if (evaluationResult.isInvalid()) {
assert mode.doNotVerify();
return evaluationResult;
}
}
- DexClass clazz = appView.definitionFor(type);
+ // TODO(b/187035453): We should visit generic signatures in the enqueuer.
+ DexClass clazz =
+ appView
+ .appInfo()
+ .definitionForWithoutExistenceAssert(appView.graphLens().lookupClassType(type));
if (clazz == null) {
// We do not know if the application of arguments works or not.
return VALID;
}
+ if (typeArguments.isEmpty()) {
+ // When type arguments are empty we are using the raw type.
+ return VALID;
+ }
if (typeArguments.size() != clazz.classSignature.getFormalTypeParameters().size()) {
assert mode.doNotVerify();
return INVALID_APPLICATION_COUNT;
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureDiagnostic.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureFormatDiagnostic.java
similarity index 78%
rename from src/main/java/com/android/tools/r8/graph/GenericSignatureDiagnostic.java
rename to src/main/java/com/android/tools/r8/graph/GenericSignatureFormatDiagnostic.java
index fa95bce..6729890 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureFormatDiagnostic.java
@@ -9,13 +9,13 @@
import com.android.tools.r8.position.Position;
import java.lang.reflect.GenericSignatureFormatError;
-public class GenericSignatureDiagnostic implements Diagnostic {
+public class GenericSignatureFormatDiagnostic implements Diagnostic {
private final Origin origin;
private final Position position;
private final String message;
- GenericSignatureDiagnostic(Origin origin, Position position, String message) {
+ GenericSignatureFormatDiagnostic(Origin origin, Position position, String message) {
this.origin = origin;
this.position = position;
this.message = message;
@@ -36,22 +36,22 @@
return message;
}
- static GenericSignatureDiagnostic invalidClassSignature(
+ static GenericSignatureFormatDiagnostic invalidClassSignature(
String signature, String name, Origin origin, GenericSignatureFormatError error) {
return invalidSignature(signature, "class", name, origin, error);
}
- static GenericSignatureDiagnostic invalidMethodSignature(
+ static GenericSignatureFormatDiagnostic invalidMethodSignature(
String signature, String name, Origin origin, GenericSignatureFormatError error) {
return invalidSignature(signature, "method", name, origin, error);
}
- static GenericSignatureDiagnostic invalidFieldSignature(
+ static GenericSignatureFormatDiagnostic invalidFieldSignature(
String signature, String name, Origin origin, GenericSignatureFormatError error) {
return invalidSignature(signature, "field", name, origin, error);
}
- private static GenericSignatureDiagnostic invalidSignature(
+ private static GenericSignatureFormatDiagnostic invalidSignature(
String signature,
String kind,
String name,
@@ -70,6 +70,6 @@
+ System.lineSeparator()
+ "Parser error: "
+ error.getMessage();
- return new GenericSignatureDiagnostic(origin, Position.UNKNOWN, message);
+ return new GenericSignatureFormatDiagnostic(origin, Position.UNKNOWN, message);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
index c381074..56a6496 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
@@ -14,52 +14,37 @@
import com.android.tools.r8.graph.GenericSignature.ReturnType;
import com.android.tools.r8.graph.GenericSignature.TypeSignature;
import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext;
import com.android.tools.r8.utils.ListUtils;
-import com.google.common.collect.ImmutableSet;
-import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
public class GenericSignaturePartialTypeArgumentApplier implements GenericSignatureVisitor {
- private final Map<String, DexType> substitutions;
- private final Set<String> liveTypeVariables;
- private final DexType objectType;
+ private final TypeParameterContext typeParameterContext;
private final BiPredicate<DexType, DexType> enclosingPruned;
private final Predicate<DexType> hasGenericTypeParameters;
+ private final AppView<?> appView;
private GenericSignaturePartialTypeArgumentApplier(
- Map<String, DexType> substitutions,
- Set<String> liveTypeVariables,
- DexType objectType,
+ AppView<?> appView,
+ TypeParameterContext typeParameterContext,
BiPredicate<DexType, DexType> enclosingPruned,
Predicate<DexType> hasGenericTypeParameters) {
- this.substitutions = substitutions;
- this.liveTypeVariables = liveTypeVariables;
- this.objectType = objectType;
+ this.appView = appView;
+ this.typeParameterContext = typeParameterContext;
this.enclosingPruned = enclosingPruned;
this.hasGenericTypeParameters = hasGenericTypeParameters;
}
public static GenericSignaturePartialTypeArgumentApplier build(
- DexType objectType,
+ AppView<?> appView,
+ TypeParameterContext typeParameterContext,
BiPredicate<DexType, DexType> enclosingPruned,
Predicate<DexType> hasGenericTypeParameters) {
return new GenericSignaturePartialTypeArgumentApplier(
- Collections.emptyMap(),
- Collections.emptySet(),
- objectType,
- enclosingPruned,
- hasGenericTypeParameters);
- }
-
- public GenericSignaturePartialTypeArgumentApplier addSubstitutionsAndVariables(
- Map<String, DexType> substitutions, Set<String> liveTypeVariables) {
- return new GenericSignaturePartialTypeArgumentApplier(
- substitutions, liveTypeVariables, objectType, enclosingPruned, hasGenericTypeParameters);
+ appView, typeParameterContext, enclosingPruned, hasGenericTypeParameters);
}
public GenericSignaturePartialTypeArgumentApplier buildForMethod(
@@ -67,23 +52,27 @@
if (formals.isEmpty()) {
return this;
}
- ImmutableSet.Builder<String> liveVariablesBuilder = ImmutableSet.builder();
- liveVariablesBuilder.addAll(liveTypeVariables);
- formals.forEach(
- formal -> {
- liveVariablesBuilder.add(formal.name);
- });
return new GenericSignaturePartialTypeArgumentApplier(
- substitutions, liveTypeVariables, objectType, enclosingPruned, hasGenericTypeParameters);
+ appView,
+ typeParameterContext.addLiveParameters(
+ ListUtils.map(formals, FormalTypeParameter::getName)),
+ enclosingPruned,
+ hasGenericTypeParameters);
}
@Override
public ClassSignature visitClassSignature(ClassSignature classSignature) {
+ if (classSignature.hasNoSignature() || classSignature.isInvalid()) {
+ return classSignature;
+ }
return classSignature.visit(this);
}
@Override
public MethodTypeSignature visitMethodSignature(MethodTypeSignature methodSignature) {
+ if (methodSignature.hasNoSignature() || methodSignature.isInvalid()) {
+ return methodSignature;
+ }
return methodSignature.visit(this);
}
@@ -111,7 +100,7 @@
@Override
public List<FieldTypeSignature> visitInterfaceBounds(List<FieldTypeSignature> fieldSignatures) {
- if (fieldSignatures == null || fieldSignatures.isEmpty()) {
+ if (fieldSignatures.isEmpty()) {
return fieldSignatures;
}
return ListUtils.mapOrElse(fieldSignatures, this::visitFieldTypeSignature);
@@ -142,6 +131,9 @@
@Override
public FieldTypeSignature visitClassBound(FieldTypeSignature fieldSignature) {
+ if (fieldSignature.hasNoSignature()) {
+ return fieldSignature;
+ }
return visitFieldTypeSignature(fieldSignature);
}
@@ -205,6 +197,9 @@
@Override
public FieldTypeSignature visitFieldTypeSignature(FieldTypeSignature fieldSignature) {
+ if (fieldSignature.hasNoSignature() || fieldSignature.isInvalid()) {
+ return fieldSignature;
+ }
if (fieldSignature.isStar()) {
return fieldSignature;
} else if (fieldSignature.isClassTypeSignature()) {
@@ -214,11 +209,10 @@
} else {
assert fieldSignature.isTypeVariableSignature();
String typeVariableName = fieldSignature.asTypeVariableSignature().typeVariable();
- if (substitutions.containsKey(typeVariableName)
- && !liveTypeVariables.contains(typeVariableName)) {
- DexType substitution = substitutions.get(typeVariableName);
+ if (!typeParameterContext.isLiveParameter(typeVariableName)) {
+ DexType substitution = typeParameterContext.getPrunedSubstitution(typeVariableName);
if (substitution == null) {
- substitution = objectType;
+ substitution = appView.dexItemFactory().objectType;
}
return new ClassTypeSignature(substitution).asArgument(WildcardIndicator.NONE);
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
index d04e3af..b111c57 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -87,15 +87,15 @@
@Override
public FieldTypeSignature visitClassBound(FieldTypeSignature fieldSignature) {
sb.append(":");
+ if (fieldSignature.hasNoSignature()) {
+ return fieldSignature;
+ }
printFieldTypeSignature(fieldSignature, false);
return fieldSignature;
}
@Override
public List<FieldTypeSignature> visitInterfaceBounds(List<FieldTypeSignature> fieldSignatures) {
- if (fieldSignatures == null) {
- return null;
- }
fieldSignatures.forEach(this::visitInterfaceBound);
return fieldSignatures;
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index da35877..332aa8d 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -212,12 +212,15 @@
@Override
public FieldTypeSignature visitClassBound(FieldTypeSignature fieldSignature) {
+ if (fieldSignature.hasNoSignature()) {
+ return fieldSignature;
+ }
return visitFieldTypeSignature(fieldSignature);
}
@Override
public List<FieldTypeSignature> visitInterfaceBounds(List<FieldTypeSignature> fieldSignatures) {
- if (fieldSignatures == null || fieldSignatures.isEmpty()) {
+ if (fieldSignatures.isEmpty()) {
return fieldSignatures;
}
return ListUtils.mapOrElse(fieldSignatures, this::visitFieldTypeSignature);
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
index c6d0e6a..59cd879 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
@@ -69,14 +69,14 @@
@Override
public FieldTypeSignature visitClassBound(FieldTypeSignature fieldSignature) {
+ if (fieldSignature.hasNoSignature()) {
+ return fieldSignature;
+ }
return visitFieldTypeSignature(fieldSignature);
}
@Override
public List<FieldTypeSignature> visitInterfaceBounds(List<FieldTypeSignature> fieldSignatures) {
- if (fieldSignatures == null) {
- return null;
- }
fieldSignatures.forEach(this::visitInterfaceBound);
return fieldSignatures;
}
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureValidationDiagnostic.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureValidationDiagnostic.java
new file mode 100644
index 0000000..4a85abd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureValidationDiagnostic.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+public class GenericSignatureValidationDiagnostic implements Diagnostic {
+
+ private final Origin origin;
+ private final Position position;
+ private final String message;
+
+ GenericSignatureValidationDiagnostic(Origin origin, Position position, String message) {
+ this.origin = origin;
+ this.position = position;
+ this.message = message;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ @Override
+ public Position getPosition() {
+ return position;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return message;
+ }
+
+ static GenericSignatureValidationDiagnostic invalidClassSignature(
+ String signature, String name, Origin origin, SignatureEvaluationResult error) {
+ return invalidSignature(signature, "class", name, origin, error);
+ }
+
+ static GenericSignatureValidationDiagnostic invalidMethodSignature(
+ String signature, String name, Origin origin, SignatureEvaluationResult error) {
+ return invalidSignature(signature, "method", name, origin, error);
+ }
+
+ static GenericSignatureValidationDiagnostic invalidFieldSignature(
+ String signature, String name, Origin origin, SignatureEvaluationResult error) {
+ return invalidSignature(signature, "field", name, origin, error);
+ }
+
+ private static GenericSignatureValidationDiagnostic invalidSignature(
+ String signature, String kind, String name, Origin origin, SignatureEvaluationResult error) {
+ String message =
+ "Invalid signature '"
+ + signature
+ + "' for "
+ + kind
+ + " "
+ + name
+ + "."
+ + System.lineSeparator()
+ + "Validation error: "
+ + error.getDescription()
+ + "."
+ + System.lineSeparator()
+ + "Signature is ignored and will not be present in the output.";
+ return new GenericSignatureValidationDiagnostic(origin, Position.UNKNOWN, message);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index ff2fd57..a07a380 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -9,9 +9,12 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.ir.desugar.itf.InterfaceProcessor.InterfaceProcessorNestedGraphLens;
+import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
+import com.android.tools.r8.optimize.MemberRebindingLens;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
@@ -457,6 +460,18 @@
return false;
}
+ public MemberRebindingLens asMemberRebindingLens() {
+ return null;
+ }
+
+ public boolean isMemberRebindingIdentityLens() {
+ return false;
+ }
+
+ public MemberRebindingIdentityLens asMemberRebindingIdentityLens() {
+ return null;
+ }
+
public abstract boolean isNonIdentityLens();
public NonIdentityGraphLens asNonIdentityLens() {
@@ -545,10 +560,34 @@
return result;
}
- public <R extends DexReference, T> ImmutableMap<R, T> rewriteReferenceKeys(Map<R, T> map) {
- ImmutableMap.Builder<R, T> builder = ImmutableMap.builder();
- map.forEach((reference, value) -> builder.put(rewriteReference(reference), value));
- return builder.build();
+ public <R extends DexReference, T> Map<R, T> rewriteReferenceKeys(
+ Map<R, T> map, Function<List<T>, T> merge) {
+ Map<R, T> result = new IdentityHashMap<>();
+ Map<R, List<T>> needsMerge = new IdentityHashMap<>();
+ map.forEach(
+ (reference, value) -> {
+ R rewrittenReference = rewriteReference(reference);
+ List<T> unmergedValues = needsMerge.get(rewrittenReference);
+ if (unmergedValues != null) {
+ unmergedValues.add(value);
+ } else {
+ T existingValue = result.put(rewrittenReference, value);
+ if (existingValue != null) {
+ // Remove this for now and let the merge function decide when all colliding values are
+ // known.
+ needsMerge.put(rewrittenReference, ListUtils.newArrayList(existingValue, value));
+ result.remove(rewrittenReference);
+ }
+ }
+ });
+ needsMerge.forEach(
+ (rewrittenReference, unmergedValues) -> {
+ T mergedValue = merge.apply(unmergedValues);
+ if (mergedValue != null) {
+ result.put(rewrittenReference, mergedValue);
+ }
+ });
+ return result;
}
public Object2BooleanMap<DexReference> rewriteReferenceKeys(Object2BooleanMap<DexReference> map) {
@@ -671,7 +710,8 @@
}
@SuppressWarnings("unchecked")
- public final <T extends GraphLens> T findPrevious(Predicate<NonIdentityGraphLens> predicate) {
+ public final <T extends NonIdentityGraphLens> T findPrevious(
+ Predicate<NonIdentityGraphLens> predicate) {
GraphLens current = getPrevious();
while (current.isNonIdentityLens()) {
NonIdentityGraphLens nonIdentityGraphLens = current.asNonIdentityLens();
@@ -707,7 +747,7 @@
@Override
public String lookupPackageName(String pkg) {
- return pkg;
+ return getPrevious().lookupPackageName(pkg);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 08bc5f0..fbefa45 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -57,7 +57,6 @@
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.MethodPosition;
@@ -228,16 +227,10 @@
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
- MethodProcessor methodProcessor) {
+ RewrittenPrototypeDescription protoChanges) {
return asCfCode()
.buildInliningIR(
- context,
- method,
- appView,
- valueNumberGenerator,
- callerPosition,
- origin,
- methodProcessor);
+ context, method, appView, valueNumberGenerator, callerPosition, origin, protoChanges);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index 4a5c689..23bd9f7 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -75,6 +75,10 @@
return this;
}
+ public static MethodAccessFlags createForClassInitializer() {
+ return fromSharedAccessFlags(Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, true);
+ }
+
public static MethodAccessFlags createPublicStaticSynthetic() {
return fromSharedAccessFlags(
Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
diff --git a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
index 8eaa46f..81724fb 100644
--- a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
@@ -62,6 +62,14 @@
return false;
}
+ public NestedGraphLens(AppView<?> appView) {
+ this(
+ appView,
+ NestedGraphLens.EMPTY_FIELD_MAP,
+ NestedGraphLens.EMPTY_METHOD_MAP,
+ NestedGraphLens.EMPTY_TYPE_MAP);
+ }
+
public NestedGraphLens(
AppView<?> appView,
BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
index 617f620..f4aa43b 100644
--- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.kotlin.KotlinMemberLevelInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
@@ -33,10 +32,10 @@
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
- MethodProcessor methodProcessor) {
+ RewrittenPrototypeDescription protoChanges) {
Code code = getDefinition().getCode();
return code.buildInliningIR(
- context, this, appView, valueNumberGenerator, callerPosition, origin, methodProcessor);
+ context, this, appView, valueNumberGenerator, callerPosition, origin, protoChanges);
}
public void collectIndexedItems(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 2b65950..dfd4f77 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -6,11 +6,8 @@
import static com.google.common.base.Predicates.not;
-import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinition;
@@ -30,7 +27,8 @@
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerSynthesizedCode;
+import com.android.tools.r8.horizontalclassmerging.code.ClassInitializerMerger;
+import com.android.tools.r8.horizontalclassmerging.code.SyntheticClassInitializerConverter;
import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
@@ -66,7 +64,7 @@
private final Mode mode;
private final MergeGroup group;
private final DexItemFactory dexItemFactory;
- private final ClassInitializerSynthesizedCode classInitializerSynthesizedCode;
+ private final ClassInitializerMerger classInitializerMerger;
private final HorizontalClassMergerGraphLens.Builder lensBuilder;
private final ClassMethodsBuilder classMethodsBuilder = new ClassMethodsBuilder();
@@ -74,7 +72,7 @@
private final ClassStaticFieldsMerger classStaticFieldsMerger;
private final ClassInstanceFieldsMerger classInstanceFieldsMerger;
private final Collection<VirtualMethodMerger> virtualMethodMergers;
- private final Collection<ConstructorMerger> constructorMergers;
+ private final Collection<InstanceInitializerMerger> instanceInitializerMergers;
private ClassMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
@@ -82,17 +80,17 @@
HorizontalClassMergerGraphLens.Builder lensBuilder,
MergeGroup group,
Collection<VirtualMethodMerger> virtualMethodMergers,
- Collection<ConstructorMerger> constructorMergers,
- ClassInitializerSynthesizedCode classInitializerSynthesizedCode) {
+ Collection<InstanceInitializerMerger> instanceInitializerMergers,
+ ClassInitializerMerger classInitializerMerger) {
this.appView = appView;
this.mode = mode;
this.lensBuilder = lensBuilder;
this.group = group;
this.virtualMethodMergers = virtualMethodMergers;
- this.constructorMergers = constructorMergers;
+ this.instanceInitializerMergers = instanceInitializerMergers;
this.dexItemFactory = appView.dexItemFactory();
- this.classInitializerSynthesizedCode = classInitializerSynthesizedCode;
+ this.classInitializerMerger = classInitializerMerger;
this.classStaticFieldsMerger = new ClassStaticFieldsMerger(appView, lensBuilder, group);
this.classInstanceFieldsMerger = new ClassInstanceFieldsMerger(appView, lensBuilder, group);
@@ -104,42 +102,46 @@
group.forEachSource(clazz -> classIdentifiers.put(clazz.getType(), classIdentifiers.size()));
}
- void mergeDirectMethods(SyntheticArgumentClass syntheticArgumentClass) {
- mergeStaticClassInitializers();
+ void mergeDirectMethods(
+ SyntheticArgumentClass syntheticArgumentClass,
+ SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+ mergeStaticClassInitializers(syntheticClassInitializerConverterBuilder);
mergeDirectMethods(group.getTarget());
group.forEachSource(this::mergeDirectMethods);
mergeConstructors(syntheticArgumentClass);
}
- void mergeStaticClassInitializers() {
- if (classInitializerSynthesizedCode.isEmpty()) {
+ void mergeStaticClassInitializers(
+ SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
+ if (classInitializerMerger.isEmpty()) {
return;
}
- DexMethod newClinit = dexItemFactory.createClassInitializer(group.getTarget().getType());
- Code code = classInitializerSynthesizedCode.getOrCreateCode(group.getTarget().getType());
- if (!group.getTarget().hasClassInitializer()) {
- classMethodsBuilder.addDirectMethod(
- new DexEncodedMethod(
- newClinit,
- MethodAccessFlags.fromSharedAccessFlags(
- Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
- MethodTypeSignature.noSignature(),
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- code,
- true,
- classInitializerSynthesizedCode.getCfVersion()));
- } else {
- DexEncodedMethod clinit = group.getTarget().getClassInitializer();
- clinit.setCode(code, appView);
- if (code.isCfCode()) {
- CfVersion cfVersion = classInitializerSynthesizedCode.getCfVersion();
- if (cfVersion != null) {
- clinit.upgradeClassFileVersion(cfVersion);
- }
- }
- classMethodsBuilder.addDirectMethod(clinit);
+ // Synthesize a new class initializer with a fresh synthetic original name.
+ DexMethod newMethodReference =
+ dexItemFactory.createClassInitializer(group.getTarget().getType());
+ DexMethod syntheticMethodReference =
+ newMethodReference.withName("$r8$clinit$synthetic", dexItemFactory);
+ lensBuilder.recordNewMethodSignature(syntheticMethodReference, newMethodReference, true);
+
+ DexEncodedMethod definition =
+ new DexEncodedMethod(
+ newMethodReference,
+ MethodAccessFlags.createForClassInitializer(),
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ classInitializerMerger.getCode(syntheticMethodReference),
+ DexEncodedMethod.D8_R8_SYNTHESIZED,
+ classInitializerMerger.getCfVersion());
+ classMethodsBuilder.addDirectMethod(definition);
+
+ // In case we didn't synthesize CF code, we register the class initializer for conversion to dex
+ // after merging.
+ if (!definition.getCode().isCfCode()) {
+ assert appView.options().isGeneratingDex();
+ assert mode.isFinal();
+ syntheticClassInitializerConverterBuilder.add(group);
}
}
@@ -182,13 +184,10 @@
}
void mergeConstructors(SyntheticArgumentClass syntheticArgumentClass) {
- constructorMergers.forEach(
+ instanceInitializerMergers.forEach(
merger ->
merger.merge(
- classMethodsBuilder,
- lensBuilder,
- classIdentifiers,
- syntheticArgumentClass));
+ classMethodsBuilder, lensBuilder, classIdentifiers, syntheticArgumentClass));
}
void mergeVirtualMethods() {
@@ -270,15 +269,14 @@
}
private void mergeInterfaces() {
- DexTypeList previousInterfaces = group.getTarget().getInterfaces();
- Set<DexType> interfaces = Sets.newLinkedHashSet(previousInterfaces);
+ Set<DexType> interfaces = Sets.newLinkedHashSet();
if (group.isInterfaceGroup()) {
// Add all implemented interfaces from the merge group to the target class, ignoring
// implemented interfaces that are part of the merge group.
Set<DexType> groupTypes =
SetUtils.newImmutableSet(
builder -> group.forEach(clazz -> builder.accept(clazz.getType())));
- group.forEachSource(
+ group.forEach(
clazz -> {
for (DexType itf : clazz.getInterfaces()) {
if (!groupTypes.contains(itf)) {
@@ -288,7 +286,7 @@
});
} else {
// Add all implemented interfaces from the merge group to the target class.
- group.forEachSource(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
+ group.forEach(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
}
group.getTarget().setInterfaces(DexTypeList.create(interfaces));
}
@@ -302,7 +300,9 @@
group.getTarget().setInstanceFields(classInstanceFieldsMerger.merge());
}
- public void mergeGroup(SyntheticArgumentClass syntheticArgumentClass) {
+ public void mergeGroup(
+ SyntheticArgumentClass syntheticArgumentClass,
+ SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
fixAccessFlags();
fixNestMemberAttributes();
@@ -314,7 +314,7 @@
mergeInterfaces();
mergeVirtualMethods();
- mergeDirectMethods(syntheticArgumentClass);
+ mergeDirectMethods(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
classMethodsBuilder.setClassMethods(group.getTarget());
mergeStaticFields();
@@ -355,25 +355,25 @@
target = current;
}
}
- group.setTarget(appView.testing().horizontalClassMergingTarget.apply(candidates, target));
+ group.setTarget(
+ appView.testing().horizontalClassMergingTarget.apply(appView, candidates, target));
}
- private ClassInitializerSynthesizedCode createClassInitializerMerger() {
- ClassInitializerSynthesizedCode.Builder builder =
- new ClassInitializerSynthesizedCode.Builder();
+ private ClassInitializerMerger createClassInitializerMerger() {
+ ClassInitializerMerger.Builder builder = new ClassInitializerMerger.Builder();
group.forEach(
clazz -> {
if (clazz.hasClassInitializer()) {
- builder.add(clazz.getClassInitializer());
+ builder.add(clazz.getProgramClassInitializer());
}
});
return builder.build();
}
- private List<ConstructorMerger> createInstanceInitializerMergers() {
- List<ConstructorMerger> constructorMergers = new ArrayList<>();
+ private List<InstanceInitializerMerger> createInstanceInitializerMergers() {
+ List<InstanceInitializerMerger> instanceInitializerMergers = new ArrayList<>();
if (appView.options().horizontalClassMergerOptions().isConstructorMergingEnabled()) {
- Map<DexProto, ConstructorMerger.Builder> buildersByProto = new LinkedHashMap<>();
+ Map<DexProto, InstanceInitializerMerger.Builder> buildersByProto = new LinkedHashMap<>();
group.forEach(
clazz ->
clazz.forEachProgramDirectMethodMatching(
@@ -382,10 +382,10 @@
buildersByProto
.computeIfAbsent(
method.getDefinition().getProto(),
- ignore -> new ConstructorMerger.Builder(appView))
- .add(method.getDefinition())));
- for (ConstructorMerger.Builder builder : buildersByProto.values()) {
- constructorMergers.addAll(builder.build(group));
+ ignore -> new InstanceInitializerMerger.Builder(appView, mode))
+ .add(method)));
+ for (InstanceInitializerMerger.Builder builder : buildersByProto.values()) {
+ instanceInitializerMergers.addAll(builder.build(group));
}
} else {
group.forEach(
@@ -393,16 +393,17 @@
clazz.forEachProgramDirectMethodMatching(
DexEncodedMethod::isInstanceInitializer,
method ->
- constructorMergers.addAll(
- new ConstructorMerger.Builder(appView)
- .add(method.getDefinition())
+ instanceInitializerMergers.addAll(
+ new InstanceInitializerMerger.Builder(appView, mode)
+ .add(method)
.build(group))));
}
// Try and merge the constructors with the most arguments first, to avoid using synthetic
// arguments if possible.
- constructorMergers.sort(Comparator.comparing(ConstructorMerger::getArity).reversed());
- return constructorMergers;
+ instanceInitializerMergers.sort(
+ Comparator.comparing(InstanceInitializerMerger::getArity).reversed());
+ return instanceInitializerMergers;
}
private List<VirtualMethodMerger> createVirtualMethodMergers() {
@@ -443,6 +444,7 @@
virtualMethodMergers.stream()
.anyMatch(virtualMethodMerger -> !virtualMethodMerger.isNopOrTrivial());
if (requiresClassIdField) {
+ assert mode.isInitial();
createClassIdField();
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
deleted file mode 100644
index fb98a86..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ConstructorMerger.java
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging;
-
-import static com.android.tools.r8.dex.Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX;
-
-import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.horizontalclassmerging.code.ConstructorEntryPointSynthesizedCode;
-import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
-import com.android.tools.r8.ir.conversion.ExtraParameter;
-import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
-import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.structural.Ordered;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-public class ConstructorMerger {
- private final AppView<?> appView;
- private final MergeGroup group;
- private final Collection<DexEncodedMethod> constructors;
- private final DexItemFactory dexItemFactory;
-
- ConstructorMerger(
- AppView<?> appView, MergeGroup group, Collection<DexEncodedMethod> constructors) {
- this.appView = appView;
- this.group = group;
- this.constructors = constructors;
-
- // Constructors should not be empty and all constructors should have the same prototype.
- assert !constructors.isEmpty();
- assert constructors.stream().map(DexEncodedMethod::getProto).distinct().count() == 1;
-
- this.dexItemFactory = appView.dexItemFactory();
- }
-
- /**
- * The method reference template describes which arguments the constructor must have, and is used
- * to generate the final reference by appending null arguments until it is fresh.
- */
- private DexMethod generateReferenceMethodTemplate() {
- DexMethod methodTemplate = constructors.iterator().next().getReference();
- if (!isTrivialMerge()) {
- methodTemplate = dexItemFactory.appendTypeToMethod(methodTemplate, dexItemFactory.intType);
- }
- return methodTemplate;
- }
-
- public int getArity() {
- return constructors.iterator().next().getReference().getArity();
- }
-
- public static class Builder {
- private int estimatedDexCodeSize;
- private final List<List<DexEncodedMethod>> constructorGroups = new ArrayList<>();
- private final AppView<? extends AppInfoWithClassHierarchy> appView;
-
- public Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
- this.appView = appView;
- createNewGroup();
- }
-
- private void createNewGroup() {
- estimatedDexCodeSize = 0;
- constructorGroups.add(new ArrayList<>());
- }
-
- public Builder add(DexEncodedMethod constructor) {
- int estimatedMaxSizeInBytes = constructor.getCode().estimatedDexCodeSizeUpperBoundInBytes();
- // If the constructor gets too large, then the constructor should be merged into a new group.
- if (estimatedDexCodeSize + estimatedMaxSizeInBytes
- > appView.options().minimumVerificationSizeLimitInBytes() / 2
- && estimatedDexCodeSize > 0) {
- createNewGroup();
- }
-
- ListUtils.last(constructorGroups).add(constructor);
- estimatedDexCodeSize += estimatedMaxSizeInBytes;
- return this;
- }
-
- public List<ConstructorMerger> build(MergeGroup group) {
- assert constructorGroups.stream().noneMatch(List::isEmpty);
- return ListUtils.map(
- constructorGroups, constructors -> new ConstructorMerger(appView, group, constructors));
- }
- }
-
- private boolean isTrivialMerge() {
- return constructors.size() == 1;
- }
-
- private DexMethod moveConstructor(
- ClassMethodsBuilder classMethodsBuilder, DexEncodedMethod constructor) {
- DexMethod method =
- dexItemFactory.createFreshMethodNameWithHolder(
- TEMPORARY_INSTANCE_INITIALIZER_PREFIX,
- constructor.getHolderType(),
- constructor.getProto(),
- group.getTarget().getType(),
- classMethodsBuilder::isFresh);
-
- DexEncodedMethod encodedMethod = constructor.toTypeSubstitutedMethod(method);
- encodedMethod.getMutableOptimizationInfo().markForceInline();
- encodedMethod.getAccessFlags().unsetConstructor();
- encodedMethod.getAccessFlags().unsetPublic();
- encodedMethod.getAccessFlags().unsetProtected();
- encodedMethod.getAccessFlags().setPrivate();
- classMethodsBuilder.addDirectMethod(encodedMethod);
-
- return method;
- }
-
- private MethodAccessFlags getAccessFlags() {
- // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
- return MethodAccessFlags.fromSharedAccessFlags(
- Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true);
- }
-
- /** Synthesize a new method which selects the constructor based on a parameter type. */
- void merge(
- ClassMethodsBuilder classMethodsBuilder,
- HorizontalClassMergerGraphLens.Builder lensBuilder,
- Reference2IntMap<DexType> classIdentifiers,
- SyntheticArgumentClass syntheticArgumentClass) {
- // Tree map as must be sorted.
- Int2ReferenceSortedMap<DexMethod> typeConstructorClassMap = new Int2ReferenceAVLTreeMap<>();
-
- CfVersion classFileVersion = null;
- for (DexEncodedMethod constructor : constructors) {
- if (constructor.hasClassFileVersion()) {
- classFileVersion =
- Ordered.maxIgnoreNull(classFileVersion, constructor.getClassFileVersion());
- }
- DexMethod movedConstructor = moveConstructor(classMethodsBuilder, constructor);
- lensBuilder.mapMethod(movedConstructor, movedConstructor);
- lensBuilder.recordNewMethodSignature(constructor.getReference(), movedConstructor);
- typeConstructorClassMap.put(
- classIdentifiers.getInt(constructor.getHolderType()), movedConstructor);
- }
-
- DexMethod methodReferenceTemplate = generateReferenceMethodTemplate();
- DexMethod newConstructorReference =
- dexItemFactory.createInstanceInitializerWithFreshProto(
- methodReferenceTemplate.withHolder(group.getTarget().getType(), dexItemFactory),
- syntheticArgumentClass.getArgumentClasses(),
- classMethodsBuilder::isFresh);
- int extraNulls = newConstructorReference.getArity() - methodReferenceTemplate.getArity();
-
- DexEncodedMethod representative = constructors.iterator().next();
- DexMethod originalConstructorReference =
- appView.graphLens().getOriginalMethodSignature(representative.getReference());
-
- // Create a special original method signature for the synthesized constructor that did not exist
- // prior to horizontal class merging. Otherwise we might accidentally think that the synthesized
- // constructor corresponds to the previous <init>() method on the target class, which could have
- // unintended side-effects such as leading to unused argument removal being applied to the
- // synthesized constructor all-though it by construction doesn't have any unused arguments.
- DexMethod bridgeConstructorReference =
- dexItemFactory.createFreshMethodNameWithoutHolder(
- "$r8$init$bridge",
- originalConstructorReference.getProto(),
- originalConstructorReference.getHolderType(),
- classMethodsBuilder::isFresh);
-
- ConstructorEntryPointSynthesizedCode synthesizedCode =
- new ConstructorEntryPointSynthesizedCode(
- typeConstructorClassMap,
- newConstructorReference,
- group.hasClassIdField() ? group.getClassIdField() : null,
- bridgeConstructorReference);
- DexEncodedMethod newConstructor =
- new DexEncodedMethod(
- newConstructorReference,
- getAccessFlags(),
- MethodTypeSignature.noSignature(),
- DexAnnotationSet.empty(),
- ParameterAnnotationsList.empty(),
- synthesizedCode,
- true,
- classFileVersion);
-
- // Map each old constructor to the newly synthesized constructor in the graph lens.
- for (DexEncodedMethod oldConstructor : constructors) {
- List<ExtraParameter> extraParameters = new ArrayList<>();
- if (constructors.size() > 1) {
- int classIdentifier = classIdentifiers.getInt(oldConstructor.getHolderType());
- extraParameters.add(new ExtraConstantIntParameter(classIdentifier));
- }
- extraParameters.addAll(Collections.nCopies(extraNulls, new ExtraUnusedNullParameter()));
- lensBuilder.mapMergedConstructor(
- oldConstructor.getReference(), newConstructorReference, extraParameters);
- }
-
- // Add a mapping from a synthetic name to the synthetic constructor.
- lensBuilder.recordNewMethodSignature(bridgeConstructorReference, newConstructorReference);
-
- classMethodsBuilder.addDirectMethod(newConstructor);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 1a88983..c05af00 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -9,16 +9,20 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.horizontalclassmerging.code.SyntheticClassInitializerConverter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
public class HorizontalClassMerger {
@@ -55,17 +59,21 @@
return new HorizontalClassMerger(appView, Mode.FINAL);
}
- public void runIfNecessary(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
+ public void runIfNecessary(
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo, ExecutorService executorService, Timing timing)
+ throws ExecutionException {
if (options.isEnabled(mode)) {
timing.begin("HorizontalClassMerger (" + mode.toString() + ")");
- run(runtimeTypeCheckInfo, timing);
+ run(runtimeTypeCheckInfo, executorService, timing);
timing.end();
} else {
appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty(), mode);
}
}
- private void run(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
+ private void run(
+ RuntimeTypeCheckInfo runtimeTypeCheckInfo, ExecutorService executorService, Timing timing)
+ throws ExecutionException {
// Run the policies on all program classes to produce a final grouping.
List<Policy> policies = PolicyScheduler.getPolicies(appView, mode, runtimeTypeCheckInfo);
Collection<MergeGroup> groups = new PolicyExecutor().run(getInitialGroups(), policies, timing);
@@ -85,7 +93,17 @@
mode.isInitial()
? new SyntheticArgumentClass.Builder(appView.withLiveness()).build(groups)
: null;
- applyClassMergers(classMergers, syntheticArgumentClass);
+ SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder =
+ SyntheticClassInitializerConverter.builder(appView);
+ applyClassMergers(
+ classMergers, syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
+
+ SyntheticClassInitializerConverter syntheticClassInitializerConverter =
+ syntheticClassInitializerConverterBuilder.build();
+ if (!syntheticClassInitializerConverter.isEmpty()) {
+ assert mode.isFinal();
+ syntheticClassInitializerConverterBuilder.build().convert(executorService);
+ }
// Generate the graph lens.
HorizontallyMergedClasses mergedClasses =
@@ -95,6 +113,8 @@
HorizontalClassMergerGraphLens horizontalClassMergerGraphLens =
createLens(mergedClasses, lensBuilder, syntheticArgumentClass);
+ assert verifyNoCyclesInInterfaceHierarchies(groups);
+
// Prune keep info.
KeepInfoCollection keepInfo = appView.getKeepInfo();
keepInfo.mutate(mutator -> mutator.removeKeepInfoForPrunedItems(mergedClasses.getSources()));
@@ -186,9 +206,11 @@
/** Merges all class groups using {@link ClassMerger}. */
private void applyClassMergers(
- Collection<ClassMerger> classMergers, SyntheticArgumentClass syntheticArgumentClass) {
+ Collection<ClassMerger> classMergers,
+ SyntheticArgumentClass syntheticArgumentClass,
+ SyntheticClassInitializerConverter.Builder syntheticClassInitializerConverterBuilder) {
for (ClassMerger merger : classMergers) {
- merger.mergeGroup(syntheticArgumentClass);
+ merger.mergeGroup(syntheticArgumentClass, syntheticClassInitializerConverterBuilder);
}
}
@@ -203,4 +225,23 @@
return new TreeFixer(appView, mergedClasses, lensBuilder, syntheticArgumentClass)
.fixupTypeReferences();
}
+
+ private boolean verifyNoCyclesInInterfaceHierarchies(Collection<MergeGroup> groups) {
+ for (MergeGroup group : groups) {
+ if (group.isClassGroup()) {
+ continue;
+ }
+ DexProgramClass interfaceClass = group.getTarget();
+ appView
+ .appInfo()
+ .traverseSuperTypes(
+ interfaceClass,
+ (superType, subclass, isInterface) -> {
+ assert superType != interfaceClass.getType()
+ : "Interface " + interfaceClass.getTypeName() + " inherits from itself";
+ return TraversalContinuation.CONTINUE;
+ });
+ }
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index 4358dc8..12a4190 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -149,12 +149,24 @@
}
void moveMethod(DexMethod from, DexMethod to) {
+ moveMethod(from, to, false);
+ }
+
+ void moveMethod(DexMethod from, DexMethod to, boolean isRepresentative) {
mapMethod(from, to);
- recordNewMethodSignature(from, to);
+ recordNewMethodSignature(from, to, isRepresentative);
}
void recordNewMethodSignature(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
+ recordNewMethodSignature(oldMethodSignature, newMethodSignature, false);
+ }
+
+ void recordNewMethodSignature(
+ DexMethod oldMethodSignature, DexMethod newMethodSignature, boolean isRepresentative) {
newMethodSignatures.put(oldMethodSignature, newMethodSignature);
+ if (isRepresentative) {
+ newMethodSignatures.setRepresentative(newMethodSignature, oldMethodSignature);
+ }
}
void fixupMethod(DexMethod oldMethodSignature, DexMethod newMethodSignature) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
new file mode 100644
index 0000000..b1f02a9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -0,0 +1,315 @@
+// Copyright (c) 2020, 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.horizontalclassmerging;
+
+import static com.android.tools.r8.dex.Constants.TEMPORARY_INSTANCE_INITIALIZER_PREFIX;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.code.ConstructorEntryPointSynthesizedCode;
+import com.android.tools.r8.ir.conversion.ExtraConstantIntParameter;
+import com.android.tools.r8.ir.conversion.ExtraParameter;
+import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.structural.Ordered;
+import com.google.common.collect.Iterables;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+public class InstanceInitializerMerger {
+
+ private final AppView<?> appView;
+ private final DexItemFactory dexItemFactory;
+ private final MergeGroup group;
+ private final List<ProgramMethod> instanceInitializers;
+ private final Mode mode;
+
+ InstanceInitializerMerger(
+ AppView<?> appView, MergeGroup group, List<ProgramMethod> instanceInitializers, Mode mode) {
+ this.appView = appView;
+ this.dexItemFactory = appView.dexItemFactory();
+ this.group = group;
+ this.instanceInitializers = instanceInitializers;
+ this.mode = mode;
+
+ // Constructors should not be empty and all constructors should have the same prototype.
+ assert !instanceInitializers.isEmpty();
+ assert instanceInitializers.stream().map(ProgramMethod::getProto).distinct().count() == 1;
+ }
+
+ /**
+ * The method reference template describes which arguments the constructor must have, and is used
+ * to generate the final reference by appending null arguments until it is fresh.
+ */
+ private DexMethod generateReferenceMethodTemplate() {
+ DexMethod methodTemplate = instanceInitializers.iterator().next().getReference();
+ if (instanceInitializers.size() > 1) {
+ methodTemplate = dexItemFactory.appendTypeToMethod(methodTemplate, dexItemFactory.intType);
+ }
+ return methodTemplate.withHolder(group.getTarget(), dexItemFactory);
+ }
+
+ public int getArity() {
+ return instanceInitializers.iterator().next().getReference().getArity();
+ }
+
+ public static class Builder {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private int estimatedDexCodeSize;
+ private final List<List<ProgramMethod>> instanceInitializerGroups = new ArrayList<>();
+ private final Mode mode;
+
+ public Builder(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+ this.appView = appView;
+ this.mode = mode;
+ createNewGroup();
+ }
+
+ private void createNewGroup() {
+ estimatedDexCodeSize = 0;
+ instanceInitializerGroups.add(new ArrayList<>());
+ }
+
+ public Builder add(ProgramMethod instanceInitializer) {
+ int estimatedMaxSizeInBytes =
+ instanceInitializer.getDefinition().getCode().estimatedDexCodeSizeUpperBoundInBytes();
+ // If the constructor gets too large, then the constructor should be merged into a new group.
+ if (estimatedDexCodeSize + estimatedMaxSizeInBytes
+ > appView.options().minimumVerificationSizeLimitInBytes() / 2
+ && estimatedDexCodeSize > 0) {
+ createNewGroup();
+ }
+
+ ListUtils.last(instanceInitializerGroups).add(instanceInitializer);
+ estimatedDexCodeSize += estimatedMaxSizeInBytes;
+ return this;
+ }
+
+ public List<InstanceInitializerMerger> build(MergeGroup group) {
+ assert instanceInitializerGroups.stream().noneMatch(List::isEmpty);
+ return ListUtils.map(
+ instanceInitializerGroups,
+ instanceInitializers ->
+ new InstanceInitializerMerger(appView, group, instanceInitializers, mode));
+ }
+ }
+
+ // Returns true if we can simply use an existing constructor as the new constructor.
+ private boolean isTrivialMerge(ClassMethodsBuilder classMethodsBuilder) {
+ if (group.hasClassIdField()) {
+ // We need to set the class id field.
+ return false;
+ }
+ DexMethod trivialInstanceInitializerReference =
+ ListUtils.first(instanceInitializers)
+ .getReference()
+ .withHolder(group.getTarget(), dexItemFactory);
+ if (!classMethodsBuilder.isFresh(trivialInstanceInitializerReference)) {
+ // We need to append null arguments for disambiguation.
+ return false;
+ }
+ return isMergeOfEquivalentInstanceInitializers();
+ }
+
+ private boolean isMergeOfEquivalentInstanceInitializers() {
+ Iterator<ProgramMethod> instanceInitializerIterator = instanceInitializers.iterator();
+ ProgramMethod firstInstanceInitializer = instanceInitializerIterator.next();
+ if (!instanceInitializerIterator.hasNext()) {
+ return true;
+ }
+ // We need all the constructors to be equivalent.
+ InstanceInitializerInfo instanceInitializerInfo =
+ firstInstanceInitializer
+ .getDefinition()
+ .getOptimizationInfo()
+ .getContextInsensitiveInstanceInitializerInfo();
+ if (!instanceInitializerInfo.hasParent()) {
+ // We don't know the parent constructor of the first constructor.
+ return false;
+ }
+ DexMethod parent = instanceInitializerInfo.getParent();
+ return Iterables.all(
+ instanceInitializers,
+ instanceInitializer ->
+ isSideEffectFreeInstanceInitializerWithParent(instanceInitializer, parent));
+ }
+
+ private boolean isSideEffectFreeInstanceInitializerWithParent(
+ ProgramMethod instanceInitializer, DexMethod parent) {
+ MethodOptimizationInfo optimizationInfo =
+ instanceInitializer.getDefinition().getOptimizationInfo();
+ return !optimizationInfo.mayHaveSideEffects()
+ && optimizationInfo.getContextInsensitiveInstanceInitializerInfo().getParent() == parent;
+ }
+
+ private DexMethod moveInstanceInitializer(
+ ClassMethodsBuilder classMethodsBuilder, ProgramMethod instanceInitializer) {
+ DexMethod method =
+ dexItemFactory.createFreshMethodNameWithHolder(
+ TEMPORARY_INSTANCE_INITIALIZER_PREFIX,
+ instanceInitializer.getHolderType(),
+ instanceInitializer.getProto(),
+ group.getTarget().getType(),
+ classMethodsBuilder::isFresh);
+
+ DexEncodedMethod encodedMethod =
+ instanceInitializer.getDefinition().toTypeSubstitutedMethod(method);
+ encodedMethod.getMutableOptimizationInfo().markForceInline();
+ encodedMethod.getAccessFlags().unsetConstructor();
+ encodedMethod.getAccessFlags().unsetPublic();
+ encodedMethod.getAccessFlags().unsetProtected();
+ encodedMethod.getAccessFlags().setPrivate();
+ classMethodsBuilder.addDirectMethod(encodedMethod);
+
+ return method;
+ }
+
+ private MethodAccessFlags getAccessFlags() {
+ // TODO(b/164998929): ensure this behaviour is correct, should probably calculate upper bound
+ return MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, true);
+ }
+
+ /** Synthesize a new method which selects the constructor based on a parameter type. */
+ void merge(
+ ClassMethodsBuilder classMethodsBuilder,
+ HorizontalClassMergerGraphLens.Builder lensBuilder,
+ Reference2IntMap<DexType> classIdentifiers,
+ SyntheticArgumentClass syntheticArgumentClass) {
+ if (isTrivialMerge(classMethodsBuilder)) {
+ mergeTrivial(classMethodsBuilder, lensBuilder);
+ return;
+ }
+
+ assert mode.isInitial();
+
+ // Tree map as must be sorted.
+ Int2ReferenceSortedMap<DexMethod> typeConstructorClassMap = new Int2ReferenceAVLTreeMap<>();
+
+ // Move constructors to target class.
+ CfVersion classFileVersion = null;
+ for (ProgramMethod instanceInitializer : instanceInitializers) {
+ if (instanceInitializer.getDefinition().hasClassFileVersion()) {
+ classFileVersion =
+ Ordered.maxIgnoreNull(
+ classFileVersion, instanceInitializer.getDefinition().getClassFileVersion());
+ }
+ DexMethod movedInstanceInitializer =
+ moveInstanceInitializer(classMethodsBuilder, instanceInitializer);
+ lensBuilder.mapMethod(movedInstanceInitializer, movedInstanceInitializer);
+ lensBuilder.recordNewMethodSignature(
+ instanceInitializer.getReference(), movedInstanceInitializer);
+ typeConstructorClassMap.put(
+ classIdentifiers.getInt(instanceInitializer.getHolderType()), movedInstanceInitializer);
+ }
+
+ // Create merged constructor reference.
+ DexMethod methodReferenceTemplate = generateReferenceMethodTemplate();
+ DexMethod newConstructorReference =
+ dexItemFactory.createInstanceInitializerWithFreshProto(
+ methodReferenceTemplate,
+ syntheticArgumentClass.getArgumentClasses(),
+ classMethodsBuilder::isFresh);
+ int extraNulls = newConstructorReference.getArity() - methodReferenceTemplate.getArity();
+
+ ProgramMethod representative = ListUtils.first(instanceInitializers);
+ DexMethod originalConstructorReference =
+ appView.graphLens().getOriginalMethodSignature(representative.getReference());
+
+ // Create a special original method signature for the synthesized constructor that did not exist
+ // prior to horizontal class merging. Otherwise we might accidentally think that the synthesized
+ // constructor corresponds to the previous <init>() method on the target class, which could have
+ // unintended side-effects such as leading to unused argument removal being applied to the
+ // synthesized constructor all-though it by construction doesn't have any unused arguments.
+ DexMethod bridgeConstructorReference =
+ dexItemFactory.createFreshMethodNameWithoutHolder(
+ "$r8$init$bridge",
+ originalConstructorReference.getProto(),
+ originalConstructorReference.getHolderType(),
+ classMethodsBuilder::isFresh);
+
+ ConstructorEntryPointSynthesizedCode synthesizedCode =
+ new ConstructorEntryPointSynthesizedCode(
+ typeConstructorClassMap,
+ newConstructorReference,
+ group.hasClassIdField() ? group.getClassIdField() : null,
+ bridgeConstructorReference);
+ DexEncodedMethod newConstructor =
+ new DexEncodedMethod(
+ newConstructorReference,
+ getAccessFlags(),
+ MethodTypeSignature.noSignature(),
+ DexAnnotationSet.empty(),
+ ParameterAnnotationsList.empty(),
+ synthesizedCode,
+ true,
+ classFileVersion);
+
+ // Map each old constructor to the newly synthesized constructor in the graph lens.
+ for (ProgramMethod oldInstanceInitializer : instanceInitializers) {
+ List<ExtraParameter> extraParameters = new ArrayList<>();
+ if (instanceInitializers.size() > 1) {
+ int classIdentifier = classIdentifiers.getInt(oldInstanceInitializer.getHolderType());
+ extraParameters.add(new ExtraConstantIntParameter(classIdentifier));
+ }
+ extraParameters.addAll(Collections.nCopies(extraNulls, new ExtraUnusedNullParameter()));
+ lensBuilder.mapMergedConstructor(
+ oldInstanceInitializer.getReference(), newConstructorReference, extraParameters);
+ }
+
+ // Add a mapping from a synthetic name to the synthetic constructor.
+ lensBuilder.recordNewMethodSignature(bridgeConstructorReference, newConstructorReference);
+
+ classMethodsBuilder.addDirectMethod(newConstructor);
+ }
+
+ private void mergeTrivial(
+ ClassMethodsBuilder classMethodsBuilder, HorizontalClassMergerGraphLens.Builder lensBuilder) {
+ ProgramMethod representative = ListUtils.first(instanceInitializers);
+ DexMethod newMethodReference =
+ representative.getReference().withHolder(group.getTarget(), dexItemFactory);
+
+ for (ProgramMethod constructor : instanceInitializers) {
+ boolean isRepresentative = constructor == representative;
+ lensBuilder.moveMethod(constructor.getReference(), newMethodReference, isRepresentative);
+ }
+
+ DexEncodedMethod newMethod =
+ representative.getHolder() == group.getTarget()
+ ? representative.getDefinition()
+ : representative.getDefinition().toTypeSubstitutedMethod(newMethodReference);
+ fixupAccessFlagsForTrivialMerge(newMethod.getAccessFlags());
+
+ classMethodsBuilder.addDirectMethod(newMethod);
+ }
+
+ private void fixupAccessFlagsForTrivialMerge(MethodAccessFlags accessFlags) {
+ if (!accessFlags.isPublic()) {
+ accessFlags.unsetPrivate();
+ accessFlags.unsetProtected();
+ accessFlags.setPublic();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index ebedb0b..ba5d775 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.IteratorUtils;
import com.google.common.collect.Iterables;
@@ -108,6 +109,11 @@
return Iterables.filter(classes, clazz -> clazz != target);
}
+ public DexType getSuperType() {
+ assert IterableUtils.allIdentical(classes, DexClass::getSuperType);
+ return getClasses().getFirst().getSuperType();
+ }
+
public boolean hasTarget() {
return target != null;
}
@@ -134,6 +140,10 @@
return classes.isEmpty();
}
+ public boolean isClassGroup() {
+ return !isInterfaceGroup();
+ }
+
public boolean isInterfaceGroup() {
assert !isEmpty();
assert IterableUtils.allIdentical(getClasses(), DexClass::isInterface);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
index e5854fe..5bff09e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
@@ -22,6 +22,10 @@
public abstract String getName();
+ public boolean isIdentityForInterfaceGroups() {
+ return false;
+ }
+
public boolean isSingleClassPolicy() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index 01a85ee..6969f94 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -8,10 +8,9 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.policies.AllInstantiatedOrUninstantiated;
-import com.android.tools.r8.horizontalclassmerging.policies.AtMostOneClassInitializer;
import com.android.tools.r8.horizontalclassmerging.policies.CheckAbstractClasses;
import com.android.tools.r8.horizontalclassmerging.policies.CheckSyntheticClasses;
-import com.android.tools.r8.horizontalclassmerging.policies.LimitGroups;
+import com.android.tools.r8.horizontalclassmerging.policies.LimitClassGroups;
import com.android.tools.r8.horizontalclassmerging.policies.MinimizeInstanceFieldCasts;
import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotationClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
@@ -27,14 +26,14 @@
import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
-import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceInitializers;
+import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceInitializerMerging;
import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
-import com.android.tools.r8.horizontalclassmerging.policies.NoNonPrivateVirtualMethods;
import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
import com.android.tools.r8.horizontalclassmerging.policies.NoVerticallyMergedClasses;
+import com.android.tools.r8.horizontalclassmerging.policies.NoVirtualMethodMerging;
import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
@@ -49,6 +48,7 @@
import com.android.tools.r8.horizontalclassmerging.policies.VerifyPolicyAlwaysSatisfied;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -58,10 +58,13 @@
AppView<? extends AppInfoWithClassHierarchy> appView,
Mode mode,
RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
- return ImmutableList.<Policy>builder()
- .addAll(getSingleClassPolicies(appView, mode, runtimeTypeCheckInfo))
- .addAll(getMultiClassPolicies(appView, mode, runtimeTypeCheckInfo))
- .build();
+ List<Policy> policies =
+ ImmutableList.<Policy>builder()
+ .addAll(getSingleClassPolicies(appView, mode, runtimeTypeCheckInfo))
+ .addAll(getMultiClassPolicies(appView, mode, runtimeTypeCheckInfo))
+ .build();
+ assert verifyPolicyOrderingConstraints(policies);
+ return policies;
}
private static List<SingleClassPolicy> getSingleClassPolicies(
@@ -78,12 +81,6 @@
new NoDeadEnumLiteMaps(appViewWithLiveness, mode),
new NoIllegalInlining(appViewWithLiveness, mode),
new NoVerticallyMergedClasses(appViewWithLiveness, mode));
- } else {
- assert mode.isFinal();
- // TODO(b/181846319): Allow constructors, as long as the constructor protos remain unchanged
- // (in particular, we can't add nulls at constructor call sites).
- // TODO(b/181846319): Allow virtual methods, as long as they do not require any merging.
- builder.add(new NoInstanceInitializers(mode), new NoNonPrivateVirtualMethods(mode));
}
if (appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics()) {
@@ -166,12 +163,15 @@
} else {
assert mode.isFinal();
// TODO(b/185472598): Add support for merging class initializers with dex code.
- builder.add(new AtMostOneClassInitializer(mode), new NoConstructorCollisions(appView, mode));
+ builder.add(
+ new NoInstanceInitializerMerging(mode),
+ new NoVirtualMethodMerging(appView, mode),
+ new NoConstructorCollisions(appView, mode));
}
addMultiClassPoliciesForInterfaceMerging(appView, mode, builder);
- return builder.add(new LimitGroups(appView)).build();
+ return builder.add(new LimitClassGroups(appView)).build();
}
private static void addRequiredMultiClassPolicies(
@@ -204,8 +204,25 @@
Mode mode,
ImmutableList.Builder<Policy> builder) {
builder.add(
- new OnlyDirectlyConnectedOrUnrelatedInterfaces(appView, mode),
new NoDefaultInterfaceMethodMerging(appView, mode),
- new NoDefaultInterfaceMethodCollisions(appView, mode));
+ new NoDefaultInterfaceMethodCollisions(appView, mode),
+ new OnlyDirectlyConnectedOrUnrelatedInterfaces(appView, mode));
+ }
+
+ private static boolean verifyPolicyOrderingConstraints(List<Policy> policies) {
+ // No policies that may split interface groups are allowed to run after the
+ // OnlyDirectlyConnectedOrUnrelatedInterfaces policy. This policy ensures that interface merging
+ // does not lead to any cycles in the interface hierarchy, which may be invalidated if merge
+ // groups are split after the policy has run.
+ int onlyDirectlyConnectedOrUnrelatedInterfacesIndex =
+ ListUtils.lastIndexMatching(
+ policies, policy -> policy instanceof OnlyDirectlyConnectedOrUnrelatedInterfaces);
+ if (onlyDirectlyConnectedOrUnrelatedInterfacesIndex >= 0) {
+ for (Policy successorPolicy :
+ policies.subList(onlyDirectlyConnectedOrUnrelatedInterfacesIndex + 1, policies.size())) {
+ assert successorPolicy.isIdentityForInterfaceGroups();
+ }
+ }
+ return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
new file mode 100644
index 0000000..a720407
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -0,0 +1,295 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.code;
+
+import static java.lang.Integer.max;
+
+import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.ClasspathMethod;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NumberGenerator;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Return;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.CfVersionUtils;
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+/**
+ * Responsible for merging the class initializers in each merge group into a single class
+ * initializer.
+ */
+public class ClassInitializerMerger {
+
+ private final ImmutableList<ProgramMethod> classInitializers;
+
+ private ClassInitializerMerger(ImmutableList<ProgramMethod> classInitializers) {
+ this.classInitializers = classInitializers;
+ }
+
+ public boolean isEmpty() {
+ return classInitializers.isEmpty();
+ }
+
+ public Code getCode(DexMethod syntheticMethodReference) {
+ assert !classInitializers.isEmpty();
+ ProgramMethod firstClassInitializer = ListUtils.first(classInitializers);
+ if (firstClassInitializer.getDefinition().getCode().isCfCode()) {
+ assert IterableUtils.allIdentical(
+ classInitializers,
+ classInitializer -> classInitializer.getDefinition().getCode().isCfCode());
+ return new CfCodeBuilder().build(syntheticMethodReference);
+ }
+ return new IRProvider(classInitializers, syntheticMethodReference);
+ }
+
+ public CfVersion getCfVersion() {
+ ProgramMethod classInitializer = ListUtils.first(classInitializers);
+ if (classInitializers.size() == 1) {
+ DexEncodedMethod method = classInitializer.getDefinition();
+ return method.hasClassFileVersion() ? method.getClassFileVersion() : null;
+ }
+ if (classInitializer.getDefinition().getCode().isCfCode()) {
+ assert IterableUtils.allIdentical(
+ classInitializers, method -> method.getDefinition().getCode().isCfCode());
+ return CfVersionUtils.max(classInitializers);
+ }
+ return null;
+ }
+
+ public static class Builder {
+
+ private final ImmutableList.Builder<ProgramMethod> classInitializers = ImmutableList.builder();
+
+ public void add(ProgramMethod classInitializer) {
+ assert classInitializer.getDefinition().isClassInitializer();
+ assert classInitializer.getDefinition().hasCode();
+ classInitializers.add(classInitializer);
+ }
+
+ public ClassInitializerMerger build() {
+ return new ClassInitializerMerger(classInitializers.build());
+ }
+ }
+
+ /** Concatenates a collection of class initializers with CF code into a new piece of CF code. */
+ private class CfCodeBuilder {
+
+ private int maxStack = 0;
+ private int maxLocals = 0;
+
+ public CfCode build(DexMethod syntheticMethodReference) {
+ // Building the instructions will adjust maxStack and maxLocals. Build it here before invoking
+ // the CfCode constructor to ensure that the value passed in is the updated values.
+ Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
+ List<CfInstruction> instructions = buildInstructions(callerPosition);
+ return new CfCode(
+ syntheticMethodReference.getHolderType(),
+ maxStack,
+ maxLocals,
+ instructions,
+ Collections.emptyList(),
+ Collections.emptyList());
+ }
+
+ private List<CfInstruction> buildInstructions(Position callerPosition) {
+ List<CfInstruction> newInstructions = new ArrayList<>();
+ classInitializers.forEach(
+ classInitializer -> addCfCode(newInstructions, classInitializer, callerPosition));
+ newInstructions.add(new CfReturnVoid());
+ return newInstructions;
+ }
+
+ private void addCfCode(
+ List<CfInstruction> newInstructions, ProgramMethod method, Position callerPosition) {
+ CfCode code = method.getDefinition().getCode().asCfCode();
+ maxStack = max(maxStack, code.getMaxStack());
+ maxLocals = max(maxLocals, code.getMaxLocals());
+ CfLabel endLabel = new CfLabel();
+ boolean requiresLabel = false;
+ int index = 1;
+ for (CfInstruction instruction : code.getInstructions()) {
+ if (instruction.isPosition()) {
+ CfPosition cfPosition = instruction.asPosition();
+ Position position = cfPosition.getPosition();
+ newInstructions.add(
+ new CfPosition(
+ cfPosition.getLabel(),
+ position.hasCallerPosition()
+ ? position
+ : position.withCallerPosition(callerPosition)));
+ } else if (instruction.isReturn()) {
+ if (code.getInstructions().size() != index) {
+ newInstructions.add(new CfGoto(endLabel));
+ requiresLabel = true;
+ }
+ } else {
+ newInstructions.add(instruction);
+ }
+ index++;
+ }
+ if (requiresLabel) {
+ newInstructions.add(endLabel);
+ }
+ }
+ }
+
+ /**
+ * Provides a piece of {@link IRCode} that is the concatenation of a collection of class
+ * initializers.
+ */
+ private static class IRProvider extends Code {
+
+ private final ImmutableList<ProgramMethod> classInitializers;
+ private final DexMethod syntheticMethodReference;
+
+ private IRProvider(
+ ImmutableList<ProgramMethod> classInitializers, DexMethod syntheticMethodReference) {
+ this.classInitializers = classInitializers;
+ this.syntheticMethodReference = syntheticMethodReference;
+ }
+
+ @Override
+ public IRCode buildIR(ProgramMethod method, AppView<?> appView, Origin origin) {
+ assert !classInitializers.isEmpty();
+
+ Position callerPosition = Position.synthetic(0, syntheticMethodReference, null);
+ IRMetadata metadata = new IRMetadata();
+ NumberGenerator blockNumberGenerator = new NumberGenerator();
+ NumberGenerator valueNumberGenerator = new NumberGenerator();
+
+ BasicBlock block = new BasicBlock();
+ block.setNumber(blockNumberGenerator.next());
+
+ // Add "invoke-static <clinit>" for each of the class initializers to the exit block.
+ for (ProgramMethod classInitializer : classInitializers) {
+ block.add(
+ InvokeStatic.builder()
+ .setMethod(classInitializer.getReference())
+ .setPosition(callerPosition)
+ .build(),
+ metadata);
+ }
+
+ // Add "return-void" to exit block.
+ block.add(Return.builder().setPosition(Position.none()).build(), metadata);
+ block.setFilled();
+
+ IRCode code =
+ new IRCode(
+ appView.options(),
+ method,
+ ListUtils.newLinkedList(block),
+ valueNumberGenerator,
+ blockNumberGenerator,
+ metadata,
+ origin);
+
+ ListIterator<BasicBlock> blockIterator = code.listIterator();
+ InstructionListIterator instructionIterator = blockIterator.next().listIterator(code);
+
+ Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
+ for (ProgramMethod classInitializer : classInitializers) {
+ if (!instructionIterator.hasNext()) {
+ instructionIterator = blockIterator.next().listIterator(code);
+ }
+
+ InvokeStatic invoke = instructionIterator.next().asInvokeStatic();
+ assert invoke != null;
+
+ IRCode inliningIR =
+ classInitializer
+ .getDefinition()
+ .getCode()
+ .buildInliningIR(
+ method,
+ classInitializer,
+ appView,
+ valueNumberGenerator,
+ callerPosition,
+ classInitializer.getOrigin(),
+ RewrittenPrototypeDescription.none());
+
+ DexType downcast = null;
+ instructionIterator.previous();
+ instructionIterator.inlineInvoke(
+ appView, code, inliningIR, blockIterator, blocksToRemove, downcast);
+ }
+
+ // Cleanup.
+ code.removeBlocks(blocksToRemove);
+ code.removeAllDeadAndTrivialPhis();
+
+ return code;
+ }
+
+ @Override
+ protected int computeHashCode() {
+ throw new Unreachable();
+ }
+
+ @Override
+ protected boolean computeEquals(Object other) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public int estimatedDexCodeSizeUpperBoundInBytes() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public boolean isEmptyVoidMethod() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void registerCodeReferences(ProgramMethod method, UseRegistry registry) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public void registerCodeReferencesForDesugaring(ClasspathMethod method, UseRegistry registry) {
+ throw new Unreachable();
+ }
+
+ @Override
+ public String toString() {
+ throw new Unreachable();
+ }
+
+ @Override
+ public String toString(DexEncodedMethod method, ClassNameMapper naming) {
+ throw new Unreachable();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java
deleted file mode 100644
index 1b8a318..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerSynthesizedCode.java
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.code;
-
-import static com.android.tools.r8.utils.ConsumerUtils.apply;
-import static java.lang.Integer.max;
-
-import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.cf.code.CfGoto;
-import com.android.tools.r8.cf.code.CfInstruction;
-import com.android.tools.r8.cf.code.CfLabel;
-import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.utils.CfVersionUtils;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-public class ClassInitializerSynthesizedCode {
- private final List<DexEncodedMethod> staticClassInitializers;
- private int maxStack = 0;
- private int maxLocals = 0;
-
- private ClassInitializerSynthesizedCode(List<DexEncodedMethod> staticClassInitializers) {
- this.staticClassInitializers = staticClassInitializers;
- }
-
- public boolean isEmpty() {
- return staticClassInitializers.isEmpty();
- }
-
- private void addCfCode(List<CfInstruction> newInstructions, DexEncodedMethod method) {
- CfCode code = method.getCode().asCfCode();
- maxStack = max(maxStack, code.getMaxStack());
- maxLocals = max(maxLocals, code.getMaxLocals());
-
- CfLabel endLabel = new CfLabel();
- boolean requiresLabel = false;
- int index = 1;
- for (CfInstruction instruction : code.getInstructions()) {
- if (instruction.isReturn()) {
- if (code.getInstructions().size() != index) {
- newInstructions.add(new CfGoto(endLabel));
- requiresLabel = true;
- }
- } else {
- newInstructions.add(instruction);
- }
-
- index++;
- }
- if (requiresLabel) {
- newInstructions.add(endLabel);
- }
- }
-
- public Code getOrCreateCode(DexType originalHolder) {
- assert !staticClassInitializers.isEmpty();
-
- if (staticClassInitializers.size() == 1) {
- return staticClassInitializers.get(0).getCode();
- }
-
- // Building the instructions will adjust maxStack and maxLocals. Build it here before invoking
- // the CfCode constructor to ensure that the value passed in is the updated values.
- List<CfInstruction> instructions = buildInstructions();
- return new CfCode(
- originalHolder,
- maxStack,
- maxLocals,
- instructions,
- Collections.emptyList(),
- Collections.emptyList());
- }
-
- private List<CfInstruction> buildInstructions() {
- List<CfInstruction> newInstructions = new ArrayList<>();
- staticClassInitializers.forEach(apply(this::addCfCode, newInstructions));
- newInstructions.add(new CfReturnVoid());
- return newInstructions;
- }
-
- public DexEncodedMethod getFirst() {
- return staticClassInitializers.iterator().next();
- }
-
- public CfVersion getCfVersion() {
- if (staticClassInitializers.size() == 1) {
- DexEncodedMethod method = staticClassInitializers.get(0);
- return method.hasClassFileVersion() ? method.getClassFileVersion() : null;
- }
- assert staticClassInitializers.stream().allMatch(method -> method.getCode().isCfCode());
- return CfVersionUtils.max(staticClassInitializers);
- }
-
- public static class Builder {
- private final List<DexEncodedMethod> staticClassInitializers = new ArrayList<>();
-
- public void add(DexEncodedMethod method) {
- assert method.isClassInitializer();
- assert method.hasCode();
- staticClassInitializers.add(method);
- }
-
- public ClassInitializerSynthesizedCode build() {
- return new ClassInitializerSynthesizedCode(staticClassInitializers);
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticClassInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticClassInitializerConverter.java
new file mode 100644
index 0000000..a31333c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticClassInitializerConverter.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.code;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * Converts synthetic class initializers that have been created as a result of merging class
+ * initializers into a single class initializer to DEX.
+ */
+public class SyntheticClassInitializerConverter {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final List<MergeGroup> groups;
+
+ private SyntheticClassInitializerConverter(
+ AppView<? extends AppInfoWithClassHierarchy> appView, List<MergeGroup> groups) {
+ this.appView = appView;
+ this.groups = groups;
+ }
+
+ public static Builder builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ return new Builder(appView);
+ }
+
+ public void convert(ExecutorService executorService) throws ExecutionException {
+ // At this point the code rewritings described by repackaging and synthetic finalization have
+ // not been applied to the code objects. These code rewritings will be applied in the
+ // application writer. We therefore simulate that we are in D8, to allow building IR for each of
+ // the class initializers without applying the unapplied code rewritings, to avoid that we apply
+ // the lens more than once to the same piece of code.
+ AppView<AppInfo> appViewForConversion =
+ AppView.createForD8(AppInfo.createInitialAppInfo(appView.appInfo().app()));
+ appViewForConversion.setGraphLens(appView.graphLens());
+
+ // Build IR for each of the class initializers and finalize.
+ IRConverter converter = new IRConverter(appViewForConversion, Timing.empty());
+ ThreadUtils.processItems(
+ groups,
+ group -> {
+ ProgramMethod classInitializer = group.getTarget().getProgramClassInitializer();
+ IRCode code =
+ classInitializer
+ .getDefinition()
+ .getCode()
+ .buildIR(classInitializer, appViewForConversion, classInitializer.getOrigin());
+ converter.removeDeadCodeAndFinalizeIR(
+ code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+ },
+ executorService);
+ }
+
+ public boolean isEmpty() {
+ return groups.isEmpty();
+ }
+
+ public static class Builder {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final List<MergeGroup> groups = new ArrayList<>();
+
+ private Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ this.appView = appView;
+ }
+
+ public Builder add(MergeGroup group) {
+ this.groups.add(group);
+ return this;
+ }
+
+ public SyntheticClassInitializerConverter build() {
+ return new SyntheticClassInitializerConverter(appView, groups);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassInitializer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassInitializer.java
deleted file mode 100644
index a2b5887..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassInitializer.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-
-public class AtMostOneClassInitializer extends AtMostOneClassThatMatchesPolicy {
-
- public AtMostOneClassInitializer(Mode mode) {
- // TODO(b/182124475): Allow merging groups with multiple <clinit> methods in the final round of
- // merging.
- assert mode.isFinal();
- }
-
- @Override
- boolean atMostOneOf(DexProgramClass clazz) {
- return clazz.hasClassInitializer();
- }
-
- @Override
- public String getName() {
- return "AtMostOneClassInitializer";
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
similarity index 85%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
index c798f04..210f21d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
@@ -13,18 +13,18 @@
import java.util.Collections;
import java.util.LinkedList;
-public class LimitGroups extends MultiClassPolicy {
+public class LimitClassGroups extends MultiClassPolicy {
private final int maxGroupSize;
- public LimitGroups(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ public LimitClassGroups(AppView<? extends AppInfoWithClassHierarchy> appView) {
maxGroupSize = appView.options().horizontalClassMergerOptions().getMaxGroupSize();
assert maxGroupSize >= 2;
}
@Override
public Collection<MergeGroup> apply(MergeGroup group) {
- if (group.size() <= maxGroupSize) {
+ if (group.size() <= maxGroupSize || group.isInterfaceGroup()) {
return Collections.singletonList(group);
}
@@ -57,4 +57,9 @@
public String getName() {
return "LimitGroups";
}
+
+ @Override
+ public boolean isIdentityForInterfaceGroups() {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
index b091242..a2362bd 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
@@ -22,11 +22,13 @@
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodCollisions.InterfaceInfo;
+import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
@@ -34,6 +36,7 @@
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
/**
* This policy prevents that interface merging changes semantics of invoke-interface/invoke-virtual
@@ -153,7 +156,7 @@
Map<DexType, Map<DexMethodSignature, Set<DexMethod>>>
defaultMethodsInheritedBySubclassesPerClass =
computeDefaultMethodsInheritedBySubclassesPerProgramClass(
- classesOfInterest, inheritedDefaultMethodsPerClass, subtypingInfo);
+ classesOfInterest, inheritedDefaultMethodsPerClass, groups, subtypingInfo);
// Store the computed information for each interface that is subject to merging.
Map<DexType, InterfaceInfo> infos = new IdentityHashMap<>();
@@ -270,7 +273,16 @@
computeDefaultMethodsInheritedBySubclassesPerProgramClass(
Collection<DexProgramClass> classesOfInterest,
Map<DexType, Map<DexMethodSignature, Set<DexMethod>>> inheritedDefaultMethodsPerClass,
+ Collection<MergeGroup> groups,
SubtypingInfo subtypingInfo) {
+ // Build a mapping from class types to their merge group.
+ Map<DexType, Iterable<DexProgramClass>> classGroupsByType =
+ MapUtils.newIdentityHashMap(
+ builder ->
+ Iterables.filter(groups, MergeGroup::isClassGroup)
+ .forEach(
+ group -> group.forEach(clazz -> builder.accept(clazz.getType(), group))));
+
// Copy the map from classes to their inherited default methods.
Map<DexType, Map<DexMethodSignature, Set<DexMethod>>>
defaultMethodsInheritedBySubclassesPerClass =
@@ -279,7 +291,28 @@
new HashMap<>(),
outerValue ->
MapUtils.clone(outerValue, new HashMap<>(), SetUtils::newIdentityHashSet));
- BottomUpClassHierarchyTraversal.forProgramClasses(appView, subtypingInfo)
+
+ // Propagate data upwards. If classes A and B are in a merge group, we need to push the state
+ // for A to all of B's supertypes, and the state for B to all of A's supertypes.
+ //
+ // Therefore, it is important that we don't process any of A's supertypes until B has been
+ // processed, since that would lead to inadequate upwards propagation. To achieve this, we
+ // simulate that both A and B are subtypes of A's and B's supertypes.
+ Function<DexType, Iterable<DexType>> immediateSubtypesProvider =
+ type -> {
+ Set<DexType> immediateSubtypesAfterClassMerging = Sets.newIdentityHashSet();
+ for (DexType immediateSubtype : subtypingInfo.allImmediateSubtypes(type)) {
+ Iterable<DexProgramClass> group = classGroupsByType.get(immediateSubtype);
+ if (group != null) {
+ group.forEach(member -> immediateSubtypesAfterClassMerging.add(member.getType()));
+ } else {
+ immediateSubtypesAfterClassMerging.add(immediateSubtype);
+ }
+ }
+ return immediateSubtypesAfterClassMerging;
+ };
+
+ BottomUpClassHierarchyTraversal.forProgramClasses(appView, immediateSubtypesProvider)
.visit(
classesOfInterest,
clazz -> {
@@ -287,16 +320,20 @@
Map<DexMethodSignature, Set<DexMethod>> defaultMethodsToPropagate =
defaultMethodsInheritedBySubclassesPerClass.getOrDefault(
clazz.getType(), emptyMap());
- for (DexType supertype : clazz.allImmediateSupertypes()) {
- Map<DexMethodSignature, Set<DexMethod>>
- defaultMethodsInheritedBySubclassesForSupertype =
- defaultMethodsInheritedBySubclassesPerClass.computeIfAbsent(
- supertype, ignore -> new HashMap<>());
- defaultMethodsToPropagate.forEach(
- (signature, methods) ->
- defaultMethodsInheritedBySubclassesForSupertype
- .computeIfAbsent(signature, ignore -> Sets.newIdentityHashSet())
- .addAll(methods));
+ Iterable<DexProgramClass> group =
+ classGroupsByType.getOrDefault(clazz.getType(), IterableUtils.singleton(clazz));
+ for (DexProgramClass member : group) {
+ for (DexType supertype : member.allImmediateSupertypes()) {
+ Map<DexMethodSignature, Set<DexMethod>>
+ defaultMethodsInheritedBySubclassesForSupertype =
+ defaultMethodsInheritedBySubclassesPerClass.computeIfAbsent(
+ supertype, ignore -> new HashMap<>());
+ defaultMethodsToPropagate.forEach(
+ (signature, methods) ->
+ defaultMethodsInheritedBySubclassesForSupertype
+ .computeIfAbsent(signature, ignore -> Sets.newIdentityHashSet())
+ .addAll(methods));
+ }
}
});
defaultMethodsInheritedBySubclassesPerClass
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
new file mode 100644
index 0000000..bbc5e39
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Identifies when instance initializer merging is required and bails out. This is needed to ensure
+ * that we don't need to append extra null arguments at constructor call sites, such that the result
+ * of the final round of class merging can be described as a renaming only.
+ */
+public class NoInstanceInitializerMerging extends MultiClassPolicy {
+
+ public NoInstanceInitializerMerging(Mode mode) {
+ assert mode.isFinal();
+ }
+
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group) {
+ Map<MergeGroup, Map<DexMethodSignature, ProgramMethod>> newGroups = new LinkedHashMap<>();
+
+ for (DexProgramClass clazz : group) {
+ Map<DexMethodSignature, ProgramMethod> classSignatures = new HashMap<>();
+ clazz.forEachProgramInstanceInitializer(
+ method -> classSignatures.put(method.getMethodSignature(), method));
+
+ MergeGroup newGroup = null;
+ for (Entry<MergeGroup, Map<DexMethodSignature, ProgramMethod>> entry : newGroups.entrySet()) {
+ Map<DexMethodSignature, ProgramMethod> groupSignatures = entry.getValue();
+ if (canAddClassToGroup(classSignatures.values(), groupSignatures)) {
+ newGroup = entry.getKey();
+ groupSignatures.putAll(classSignatures);
+ break;
+ }
+ }
+
+ if (newGroup != null) {
+ newGroup.add(clazz);
+ } else {
+ newGroups.put(new MergeGroup(clazz), classSignatures);
+ }
+ }
+
+ return removeTrivialGroups(newGroups.keySet());
+ }
+
+ private boolean canAddClassToGroup(
+ Iterable<ProgramMethod> classMethods,
+ Map<DexMethodSignature, ProgramMethod> groupSignatures) {
+ for (ProgramMethod classMethod : classMethods) {
+ ProgramMethod groupMethod = groupSignatures.get(classMethod.getMethodSignature());
+ if (groupMethod != null && !equivalent(classMethod, groupMethod)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // For now, only recognize constructors with 0 parameters that call the same parent constructor.
+ private boolean equivalent(ProgramMethod method, ProgramMethod other) {
+ if (!method.getProto().getParameters().isEmpty()) {
+ return false;
+ }
+
+ MethodOptimizationInfo optimizationInfo = method.getDefinition().getOptimizationInfo();
+ InstanceInitializerInfo instanceInitializerInfo =
+ optimizationInfo.getContextInsensitiveInstanceInitializerInfo();
+ if (instanceInitializerInfo.isDefaultInstanceInitializerInfo()) {
+ return false;
+ }
+
+ InstanceInitializerInfo otherInstanceInitializerInfo =
+ other.getDefinition().getOptimizationInfo().getContextInsensitiveInstanceInitializerInfo();
+ assert otherInstanceInitializerInfo.isNonTrivialInstanceInitializerInfo();
+ if (instanceInitializerInfo.getParent() != otherInstanceInitializerInfo.getParent()) {
+ return false;
+ }
+
+ return !method.getDefinition().getOptimizationInfo().mayHaveSideEffects()
+ && !other.getDefinition().getOptimizationInfo().mayHaveSideEffects();
+ }
+
+ @Override
+ public String getName() {
+ return "NoInstanceInitializerMerging";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializers.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializers.java
deleted file mode 100644
index 0acfe13..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializers.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-
-public class NoInstanceInitializers extends SingleClassPolicy {
-
- public NoInstanceInitializers(Mode mode) {
- // TODO(b/181846319): Allow constructors, as long as the constructor protos remain unchanged
- // (in particular, we can't add nulls at constructor call sites).
- assert mode.isFinal();
- }
-
- @Override
- public boolean canMerge(DexProgramClass clazz) {
- return !clazz.getMethodCollection().hasDirectMethods(DexEncodedMethod::isInstanceInitializer);
- }
-
- @Override
- public String getName() {
- return "NoInstanceInitializers";
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNonPrivateVirtualMethods.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNonPrivateVirtualMethods.java
deleted file mode 100644
index 81358f1..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoNonPrivateVirtualMethods.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-
-public class NoNonPrivateVirtualMethods extends SingleClassPolicy {
-
- public NoNonPrivateVirtualMethods(Mode mode) {
- // TODO(b/181846319): Allow virtual methods as long as they do not require any merging.
- assert mode.isFinal();
- }
-
- @Override
- public boolean canMerge(DexProgramClass clazz) {
- return clazz.isInterface() || !clazz.getMethodCollection().hasVirtualMethods();
- }
-
- @Override
- public String getName() {
- return "NoNonPrivateVirtualMethods";
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
new file mode 100644
index 0000000..79b2bb2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2020, 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethodSignature;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Iterables;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Identifies when virtual method merging is required and bails out. This is needed to ensure that
+ * we don't need to synthesize any $r8$classId fields, such that the result of the final round of
+ * class merging can be described as a renaming only.
+ */
+public class NoVirtualMethodMerging extends MultiClassPolicy {
+
+ private final AppView<? extends AppInfoWithClassHierarchy> appView;
+
+ public NoVirtualMethodMerging(AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
+ assert mode.isFinal();
+ this.appView = appView;
+ }
+
+ @Override
+ public Collection<MergeGroup> apply(MergeGroup group) {
+ Map<MergeGroup, Map<DexMethodSignature, ProgramMethod>> newGroups = new LinkedHashMap<>();
+ for (DexProgramClass clazz : group) {
+ Map<DexMethodSignature, ProgramMethod> classMethods = new HashMap<>();
+ clazz.forEachProgramVirtualMethodMatching(
+ DexEncodedMethod::isNonAbstractVirtualMethod,
+ method -> classMethods.put(method.getMethodSignature(), method));
+
+ MergeGroup newGroup = null;
+ for (Entry<MergeGroup, Map<DexMethodSignature, ProgramMethod>> entry : newGroups.entrySet()) {
+ MergeGroup candidateGroup = entry.getKey();
+ Map<DexMethodSignature, ProgramMethod> groupMethods = entry.getValue();
+ if (canAddNonAbstractVirtualMethodsToGroup(
+ clazz, classMethods.values(), candidateGroup, groupMethods)) {
+ newGroup = candidateGroup;
+ groupMethods.putAll(classMethods);
+ break;
+ }
+ }
+
+ if (newGroup != null) {
+ newGroup.add(clazz);
+ } else {
+ newGroups.put(new MergeGroup(clazz), classMethods);
+ }
+ }
+ return removeTrivialGroups(newGroups.keySet());
+ }
+
+ private boolean canAddNonAbstractVirtualMethodsToGroup(
+ DexProgramClass clazz,
+ Collection<ProgramMethod> methods,
+ MergeGroup group,
+ Map<DexMethodSignature, ProgramMethod> groupMethods) {
+ // For each of clazz' virtual methods, check that adding these methods to the group does not
+ // require method merging.
+ for (ProgramMethod method : methods) {
+ ProgramMethod groupMethod = groupMethods.get(method.getMethodSignature());
+ if (groupMethod != null || hasNonAbstractDefinitionInHierarchy(group, method)) {
+ return false;
+ }
+ }
+ // For each of the group's virtual methods, check that adding these methods clazz does not
+ // require method merging.
+ for (ProgramMethod method : groupMethods.values()) {
+ if (hasNonAbstractDefinitionInHierarchy(clazz, method)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean hasNonAbstractDefinitionInHierarchy(MergeGroup group, ProgramMethod method) {
+ return hasNonAbstractDefinitionInSuperClass(group.getSuperType(), method)
+ || hasNonAbstractDefinitionInSuperInterface(
+ SetUtils.newIdentityHashSet(IterableUtils.flatMap(group, DexClass::getInterfaces)),
+ method);
+ }
+
+ private boolean hasNonAbstractDefinitionInHierarchy(DexProgramClass clazz, ProgramMethod method) {
+ return hasNonAbstractDefinitionInSuperClass(clazz.getSuperType(), method)
+ || hasNonAbstractDefinitionInSuperInterface(clazz.getInterfaces(), method);
+ }
+
+ private boolean hasNonAbstractDefinitionInSuperClass(DexType superType, ProgramMethod method) {
+ SingleResolutionResult resolutionResult =
+ appView
+ .appInfo()
+ .resolveMethodOnClass(method.getReference(), superType)
+ .asSingleResolution();
+ return resolutionResult != null && !resolutionResult.getResolvedMethod().isAbstract();
+ }
+
+ private boolean hasNonAbstractDefinitionInSuperInterface(
+ Iterable<DexType> interfaceTypes, ProgramMethod method) {
+ return Iterables.any(
+ interfaceTypes,
+ interfaceType -> {
+ SingleResolutionResult resolutionResult =
+ appView
+ .appInfo()
+ .resolveMethodOnInterface(interfaceType, method.getReference())
+ .asSingleResolution();
+ return resolutionResult != null && !resolutionResult.getResolvedMethod().isAbstract();
+ });
+ }
+
+ @Override
+ public String getName() {
+ return "NoVirtualMethodMerging";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
index d9e6cd1..fc457f7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
@@ -8,25 +8,26 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
import com.android.tools.r8.horizontalclassmerging.MergeGroup;
-import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
+import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterators;
-import com.google.common.collect.Sets;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.IdentityHashMap;
-import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Consumer;
+import java.util.function.Function;
/**
* This policy ensures that we do not create cycles in the class hierarchy as a result of interface
@@ -53,11 +54,15 @@
* interface J extends IK, ... {}
* </pre>
*/
-public class OnlyDirectlyConnectedOrUnrelatedInterfaces extends MultiClassPolicy {
+public class OnlyDirectlyConnectedOrUnrelatedInterfaces
+ extends MultiClassPolicyWithPreprocessing<SubtypingInfo> {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final Mode mode;
+ // The interface merge groups that this policy has committed to so far.
+ private final Map<DexProgramClass, MergeGroup> committed = new IdentityHashMap<>();
+
public OnlyDirectlyConnectedOrUnrelatedInterfaces(
AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
this.appView = appView;
@@ -65,116 +70,84 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<MergeGroup> apply(MergeGroup group, SubtypingInfo subtypingInfo) {
if (!group.isInterfaceGroup()) {
return ImmutableList.of(group);
}
- Set<DexProgramClass> classes = new LinkedHashSet<>(group.getClasses());
- Map<DexProgramClass, Set<DexProgramClass>> ineligibleForMerging =
- computeIneligibleForMergingGraph(classes);
- if (ineligibleForMerging.isEmpty()) {
- return ImmutableList.of(group);
+ List<MergeGroupWithInfo> newGroupsWithInfo = new ArrayList<>();
+ for (DexProgramClass clazz : group) {
+ Set<DexProgramClass> superInterfaces = computeSuperInterfaces(clazz);
+ Set<DexProgramClass> subInterfaces = computeSubInterfaces(clazz, subtypingInfo);
+
+ MergeGroupWithInfo newGroup = null;
+ for (MergeGroupWithInfo candidateGroup : newGroupsWithInfo) {
+ // Check if adding `clazz` to `candidateGroup` would introduce a super interface that is
+ // also a sub interface. In that case we must abort since merging would lead to a cycle in
+ // the class hierarchy.
+ if (candidateGroup.isSafeToAddSubAndSuperInterfaces(
+ clazz, subInterfaces, superInterfaces)) {
+ newGroup = candidateGroup;
+ break;
+ }
+ }
+
+ if (newGroup != null) {
+ newGroup.add(clazz, superInterfaces, subInterfaces);
+ } else {
+ newGroupsWithInfo.add(new MergeGroupWithInfo(clazz, superInterfaces, subInterfaces));
+ }
}
- // Extract sub-merge groups from the graph in such a way that all pairs of interfaces in each
- // merge group are not connected by an edge in the graph.
List<MergeGroup> newGroups = new LinkedList<>();
- while (!classes.isEmpty()) {
- Iterator<DexProgramClass> iterator = classes.iterator();
- MergeGroup newGroup = new MergeGroup(iterator.next());
- Iterators.addAll(
- newGroup,
- Iterators.filter(
- iterator,
- candidate -> !isConnectedToGroup(candidate, newGroup, ineligibleForMerging)));
+ for (MergeGroupWithInfo newGroupWithInfo : newGroupsWithInfo) {
+ MergeGroup newGroup = newGroupWithInfo.getGroup();
if (!newGroup.isTrivial()) {
newGroups.add(newGroup);
+ newGroup.forEach(clazz -> committed.put(clazz, newGroup));
}
- classes.removeAll(newGroup.getClasses());
}
return newGroups;
}
- /**
- * Computes an undirected graph, where the nodes are the interfaces from the merge group, and an
- * edge I <-> J represents that I and J are not eligible for merging.
- *
- * <p>We will insert an edge I <-> J, if interface I inherits from interface J, and the path from
- * I to J in the class hierarchy includes an interface K that is outside the merge group. Note
- * that if I extends J directly we will not insert an edge I <-> J (unless there are multiple
- * paths in the class hierarchy from I to J, and one of the paths goes through an interface
- * outside the merge group).
- */
- private Map<DexProgramClass, Set<DexProgramClass>> computeIneligibleForMergingGraph(
- Set<DexProgramClass> classes) {
- Map<DexProgramClass, Set<DexProgramClass>> ineligibleForMerging = new IdentityHashMap<>();
- for (DexProgramClass clazz : classes) {
- forEachIndirectlyReachableInterfaceInMergeGroup(
- clazz,
- classes,
- other ->
- ineligibleForMerging
- .computeIfAbsent(clazz, ignore -> Sets.newIdentityHashSet())
- .add(other));
- }
- return ineligibleForMerging;
+ private Set<DexProgramClass> computeSuperInterfaces(DexProgramClass clazz) {
+ return computeTransitiveSubOrSuperInterfaces(clazz, DexClass::getInterfaces);
}
- private void forEachIndirectlyReachableInterfaceInMergeGroup(
- DexProgramClass clazz, Set<DexProgramClass> classes, Consumer<DexProgramClass> consumer) {
- // First find the set of interfaces that can be reached via paths in the class hierarchy from
- // the given interface, without visiting any interfaces outside the merge group.
- WorkList<DexType> workList = WorkList.newIdentityWorkList(clazz.getInterfaces());
- while (workList.hasNext()) {
- DexProgramClass directlyReachableInterface =
- asProgramClassOrNull(appView.definitionFor(workList.next()));
- if (directlyReachableInterface == null) {
- continue;
- }
- // If the implemented interface is a member of the merge group, then include it's interfaces.
- if (classes.contains(directlyReachableInterface)) {
- workList.addIfNotSeen(directlyReachableInterface.getInterfaces());
- }
- }
-
- // Initialize a new worklist with the first layer of indirectly reachable interface types.
- Set<DexType> directlyReachableInterfaceTypes = workList.getSeenSet();
- workList = WorkList.newIdentityWorkList();
- for (DexType directlyReachableInterfaceType : directlyReachableInterfaceTypes) {
- DexProgramClass directlyReachableInterface =
- asProgramClassOrNull(appView.definitionFor(directlyReachableInterfaceType));
- if (directlyReachableInterface != null) {
- workList.addIfNotSeen(directlyReachableInterface.getInterfaces());
- }
- }
-
- // Report all interfaces from the merge group that are reachable in the class hierarchy from the
- // worklist.
- while (workList.hasNext()) {
- DexProgramClass indirectlyReachableInterface =
- asProgramClassOrNull(appView.definitionFor(workList.next()));
- if (indirectlyReachableInterface == null) {
- continue;
- }
- if (classes.contains(indirectlyReachableInterface)) {
- consumer.accept(indirectlyReachableInterface);
- }
- workList.addIfNotSeen(indirectlyReachableInterface.getInterfaces());
- }
+ private Set<DexProgramClass> computeSubInterfaces(
+ DexProgramClass clazz, SubtypingInfo subtypingInfo) {
+ return computeTransitiveSubOrSuperInterfaces(
+ clazz, definition -> subtypingInfo.allImmediateExtendsSubtypes(definition.getType()));
}
- private boolean isConnectedToGroup(
+ private Set<DexProgramClass> computeTransitiveSubOrSuperInterfaces(
DexProgramClass clazz,
- MergeGroup group,
- Map<DexProgramClass, Set<DexProgramClass>> ineligibleForMerging) {
- for (DexProgramClass member : group) {
- if (ineligibleForMerging.getOrDefault(clazz, Collections.emptySet()).contains(member)
- || ineligibleForMerging.getOrDefault(member, Collections.emptySet()).contains(clazz)) {
- return true;
+ Function<DexProgramClass, Iterable<DexType>> immediateSubOrSuperInterfacesProvider) {
+ WorkList<DexProgramClass> workList = WorkList.newWorkList(new LinkedHashSet<>());
+ // Intentionally not marking `clazz` as seen, since we only want the strict sub/super types.
+ workList.addIgnoringSeenSet(clazz);
+ while (workList.hasNext()) {
+ DexProgramClass interfaceDefinition = workList.next();
+ MergeGroup group = committed.get(interfaceDefinition);
+ if (group != null) {
+ workList.addIfNotSeen(group);
+ }
+ for (DexType immediateSubOrSuperInterfaceType :
+ immediateSubOrSuperInterfacesProvider.apply(interfaceDefinition)) {
+ DexProgramClass immediateSubOrSuperInterface =
+ asProgramClassOrNull(appView.definitionFor(immediateSubOrSuperInterfaceType));
+ if (immediateSubOrSuperInterface != null) {
+ workList.addIfNotSeen(immediateSubOrSuperInterface);
+ }
}
}
- return false;
+ assert !workList.isSeen(clazz);
+ return workList.getMutableSeenSet();
+ }
+
+ @Override
+ public void clear() {
+ committed.clear();
}
@Override
@@ -183,7 +156,80 @@
}
@Override
+ public SubtypingInfo preprocess(Collection<MergeGroup> groups) {
+ return new SubtypingInfo(appView);
+ }
+
+ @Override
public boolean shouldSkipPolicy() {
return !appView.options().horizontalClassMergerOptions().isInterfaceMergingEnabled(mode);
}
+
+ static class MergeGroupWithInfo {
+
+ private final MergeGroup group;
+ private final Set<DexProgramClass> members;
+ private final Set<DexProgramClass> superInterfaces;
+ private final Set<DexProgramClass> subInterfaces;
+
+ MergeGroupWithInfo(
+ DexProgramClass clazz,
+ Set<DexProgramClass> superInterfaces,
+ Set<DexProgramClass> subInterfaces) {
+ this.group = new MergeGroup(clazz);
+ this.members = SetUtils.newIdentityHashSet(clazz);
+ this.superInterfaces = superInterfaces;
+ this.subInterfaces = subInterfaces;
+ }
+
+ void add(
+ DexProgramClass clazz,
+ Set<DexProgramClass> newSuperInterfaces,
+ Set<DexProgramClass> newSubInterfaces) {
+ group.add(clazz);
+ members.add(clazz);
+ Iterables.addAll(
+ superInterfaces,
+ Iterables.filter(
+ newSuperInterfaces, superInterface -> !members.contains(superInterface)));
+ superInterfaces.remove(clazz);
+ Iterables.addAll(
+ subInterfaces,
+ Iterables.filter(newSubInterfaces, subInterface -> !members.contains(subInterface)));
+ subInterfaces.remove(clazz);
+ }
+
+ MergeGroup getGroup() {
+ return group;
+ }
+
+ boolean isSafeToAddSubAndSuperInterfaces(
+ DexProgramClass clazz,
+ Set<DexProgramClass> newSubInterfaces,
+ Set<DexProgramClass> newSuperInterfaces) {
+ // Check that adding the new sub and super interfaces to the group is safe.
+ for (DexProgramClass newSubInterface : newSubInterfaces) {
+ if (!group.contains(newSubInterface) && superInterfaces.contains(newSubInterface)) {
+ return false;
+ }
+ }
+ for (DexProgramClass newSuperInterface : newSuperInterfaces) {
+ if (!group.contains(newSuperInterface) && subInterfaces.contains(newSuperInterface)) {
+ return false;
+ }
+ }
+ // Check that adding the sub and super interfaces of the group to the current class is safe.
+ for (DexProgramClass subInterface : subInterfaces) {
+ if (subInterface != clazz && newSuperInterfaces.contains(subInterface)) {
+ return false;
+ }
+ }
+ for (DexProgramClass superInterface : superInterfaces) {
+ if (superInterface != clazz && newSubInterfaces.contains(superInterface)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index da0ed41..06e1e90 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -257,7 +257,7 @@
ImmutableList.of(builderValue, defaultInstanceValue)));
converter.removeDeadCodeAndFinalizeIR(
- dynamicMethod, code, OptimizationFeedbackSimple.getInstance(), Timing.empty());
+ code, OptimizationFeedbackSimple.getInstance(), Timing.empty());
}
public static void addInliningHeuristicsForBuilderInlining(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index 17758fa..f8aae86 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -11,13 +11,16 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
import com.android.tools.r8.shaking.DefaultEnqueuerUseRegistry;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.EnqueuerUseRegistryFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.ListIterator;
+import java.util.Map;
public class ProtoEnqueuerUseRegistry extends DefaultEnqueuerUseRegistry {
@@ -28,8 +31,9 @@
public ProtoEnqueuerUseRegistry(
AppView<? extends AppInfoWithClassHierarchy> appView,
ProgramMethod currentMethod,
- Enqueuer enqueuer) {
- super(appView, currentMethod, enqueuer);
+ Enqueuer enqueuer,
+ Map<DexReference, AndroidApiLevel> apiLevelReferenceMap) {
+ super(appView, currentMethod, enqueuer, apiLevelReferenceMap);
this.references = appView.protoShrinker().references;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index c6fdbab..ad34d29 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -82,6 +83,14 @@
continue;
}
+ if (instruction.isInvokeConstructor(appView.dexItemFactory())) {
+ if (instruction.instructionMayHaveSideEffects(
+ appView, context, SideEffectAssumption.IGNORE_RECEIVER_FIELD_ASSIGNMENTS)) {
+ return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
+ }
+ continue;
+ }
+
// For other instructions, bail out if they may have side effects.
if (instruction.instructionMayHaveSideEffects(appView, context)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 7dbc0ad..df8f00e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -595,6 +595,10 @@
return filled;
}
+ public void setFilled() {
+ filled = true;
+ }
+
public void setFilledForTesting() {
filled = true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 7bd9cf9..6917a40 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -325,6 +325,10 @@
return false;
}
ProgramMethod context = code.context();
+ if (!toBeReplaced.instructionMayHaveSideEffects(appView, context)) {
+ removeOrReplaceByDebugLocalRead();
+ return true;
+ }
if (toBeReplaced.instructionMayHaveSideEffects(
appView, context, Instruction.SideEffectAssumption.CLASS_ALREADY_INITIALIZED)) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index 953bdcf..dde7f0d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -22,6 +22,10 @@
setBlock(block);
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public int opcode() {
return Opcodes.GOTO;
@@ -121,4 +125,24 @@
public boolean isAllowedAfterThrowingInstruction() {
return true;
}
+
+ public static class Builder extends BuilderBase<Builder, Goto> {
+
+ private BasicBlock target;
+
+ public Builder setTarget(BasicBlock target) {
+ this.target = target;
+ return self();
+ }
+
+ @Override
+ public Goto build() {
+ return amend(new Goto(target));
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
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 eb5a275..a1c7d45 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
@@ -1502,6 +1502,15 @@
}
};
+ public static final SideEffectAssumption IGNORE_RECEIVER_FIELD_ASSIGNMENTS =
+ new SideEffectAssumption() {
+
+ @Override
+ public boolean canIgnoreInstanceFieldAssignmentsToReceiver() {
+ return true;
+ }
+ };
+
public static final SideEffectAssumption INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS =
new SideEffectAssumption() {
@@ -1528,6 +1537,10 @@
return false;
}
+ public boolean canIgnoreInstanceFieldAssignmentsToReceiver() {
+ return false;
+ }
+
public boolean canAssumeReceiverIsNotNull() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index b4ff341..83dfa77 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -174,7 +174,8 @@
@Override
public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
ProgramMethod context = code.context();
- if (instructionMayHaveSideEffects(appView, context)) {
+ if (instructionMayHaveSideEffects(
+ appView, context, SideEffectAssumption.IGNORE_RECEIVER_FIELD_ASSIGNMENTS)) {
return DeadInstructionResult.notDead();
}
if (!getInvokedMethod().isInstanceInitializer(appView.dexItemFactory())) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 94ab740..39f52cb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -259,7 +259,8 @@
DexEncodedMethod singleTargetDefinition = singleTarget.getDefinition();
MethodOptimizationInfo optimizationInfo = singleTargetDefinition.getOptimizationInfo();
- if (singleTargetDefinition.isInstanceInitializer()) {
+ if (assumption.canIgnoreInstanceFieldAssignmentsToReceiver()
+ && singleTargetDefinition.isInstanceInitializer()) {
assert isInvokeDirect();
InstanceInitializerInfo initializerInfo =
optimizationInfo.getInstanceInitializerInfo(asInvokeDirect());
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
index 0d054ef..6cba1ee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -101,6 +101,10 @@
return position;
}
+ public boolean hasCallerPosition() {
+ return callerPosition != null;
+ }
+
@Override
public Position self() {
return this;
@@ -133,6 +137,10 @@
return lastPosition;
}
+ public Position withCallerPosition(Position callerPosition) {
+ return new Position(line, file, method, callerPosition, synthetic);
+ }
+
@Override
public boolean equals(Object other) {
return other instanceof Position && compareTo((Position) other) == 0;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 19c9887..e2f81a9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -28,6 +28,10 @@
super(value);
}
+ public static Builder builder() {
+ return new Builder();
+ }
+
@Override
public int opcode() {
return Opcodes.RETURN;
@@ -126,4 +130,17 @@
builder.add(
isReturnVoid() ? new CfReturnVoid() : new CfReturn(ValueType.fromType(getReturnType())));
}
+
+ public static class Builder extends BuilderBase<Builder, Return> {
+
+ @Override
+ public Return build() {
+ return amend(new Return());
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 7d332f0..e01cd47 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -180,7 +180,7 @@
DexBuilder.removeRedundantDebugPositions(code);
CfCode code = buildCfCode();
assert verifyInvokeInterface(code, appView);
- assert code.verifyFrames(method, appView, this.code.origin, false).isValid();
+ assert code.verifyFrames(method, appView, this.code.origin).isValid();
return code;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 0437f8d..4ae55f6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -114,7 +114,6 @@
import com.android.tools.r8.ir.code.ValueTypeConstraint;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
-import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -444,28 +443,22 @@
AppView<?> appView,
SourceCode source,
Origin origin,
- MethodProcessor processor,
- NumberGenerator valueNumberGenerator) {
- RewrittenPrototypeDescription protoChanges =
- processor.shouldApplyCodeRewritings(method)
- ? lookupPrototypeChanges(appView, method)
- : RewrittenPrototypeDescription.none();
+ NumberGenerator valueNumberGenerator,
+ RewrittenPrototypeDescription protoChanges) {
return new IRBuilder(method, appView, source, origin, protoChanges, valueNumberGenerator);
}
- private static RewrittenPrototypeDescription lookupPrototypeChanges(
+ public static RewrittenPrototypeDescription lookupPrototypeChanges(
AppView<?> appView, ProgramMethod method) {
- RewrittenPrototypeDescription prototypeChanges =
- appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference());
- if (Log.ENABLED && prototypeChanges.getArgumentInfoCollection().hasRemovedArguments()) {
- Log.info(
- IRBuilder.class,
- "Removed "
- + prototypeChanges.getArgumentInfoCollection().numberOfRemovedArguments()
- + " arguments from "
- + method.toSourceString());
+ return appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference());
+ }
+
+ public static RewrittenPrototypeDescription lookupPrototypeChangesForInlinee(
+ AppView<?> appView, ProgramMethod method, MethodProcessor methodProcessor) {
+ if (methodProcessor.shouldApplyCodeRewritings(method)) {
+ return appView.graphLens().lookupPrototypeChangesForMethodDefinition(method.getReference());
}
- return prototypeChanges;
+ return RewrittenPrototypeDescription.none();
}
private IRBuilder(
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 785fb64..fea728d 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
@@ -182,7 +182,6 @@
* (i.e., whether we are running R8). See {@link AppView#enableWholeProgramOptimizations()}.
*/
public IRConverter(AppView<?> appView, Timing timing, CfgPrinter printer) {
- assert appView.appInfo().hasLiveness() || appView.graphLens().isIdentityLens();
assert appView.options() != null;
assert appView.options().programConsumer != null;
assert timing != null;
@@ -816,7 +815,7 @@
outliner.applyOutliningCandidate(code);
printMethod(code, "IR after outlining (SSA)", null);
removeDeadCodeAndFinalizeIR(
- code.context(), code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+ code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
},
executorService);
feedback.updateVisibleOptimizationInfo();
@@ -941,8 +940,7 @@
IRCode code = method.buildIR(appView);
assert code != null;
codeRewriter.rewriteMoveResult(code);
- removeDeadCodeAndFinalizeIR(
- method, code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
+ removeDeadCodeAndFinalizeIR(code, OptimizationFeedbackIgnore.getInstance(), Timing.empty());
}
private void generateDesugaredLibraryAPIWrappers(
@@ -1475,7 +1473,8 @@
previous = printMethod(code, "IR after class inlining (SSA)", previous);
- if (interfaceMethodRewriter != null) {
+ // TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
+ if (interfaceMethodRewriter != null && appView.enableWholeProgramOptimizations()) {
timing.begin("Rewrite interface methods");
interfaceMethodRewriter.rewriteMethodReferences(
code, methodProcessor, methodProcessingContext);
@@ -1662,7 +1661,7 @@
}
public void removeDeadCodeAndFinalizeIR(
- ProgramMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+ IRCode code, OptimizationFeedback feedback, Timing timing) {
if (stringSwitchRemover != null) {
stringSwitchRemover.run(code);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
index 50bec23..a377238 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
@@ -311,7 +311,7 @@
private final BasicBlock continuationBlock;
private final DexItemFactory dexItemFactory;
- private final Phi idValue;
+ private final Phi intermediateIdValue;
private final Value stringValue;
Builder(
@@ -321,10 +321,38 @@
Value stringValue) {
this.continuationBlock = continuationBlock;
this.dexItemFactory = dexItemFactory;
- this.idValue = idValue;
+ this.intermediateIdValue = getIntermediateIdValueOrElse(idValue, idValue);
this.stringValue = stringValue;
}
+ // Finds the intermediate id value from the given (non-intermediate) id value. If the non-
+ // intermediate id value v_id has the following structure, then v_id_intermediate is returned.
+ //
+ // v_id_intermediate = phi(v1(0), v2(1), v_n(n-1))
+ // v_id = phi(v0(-1), v_id_intermediate)
+ //
+ // Normally, this intermediate value is not present, and the code will have the following
+ // structure:
+ //
+ // v_id = phi(v0(-1), v1(0), v2(1), v_n(n-1))
+ private Phi getIntermediateIdValueOrElse(Phi idValue, Phi defaultValue) {
+ if (idValue.getOperands().size() != 2) {
+ return defaultValue;
+ }
+ Phi intermediateIdValue = null;
+ for (Value operand : idValue.getOperands()) {
+ if (operand.isPhi()) {
+ if (intermediateIdValue != null) {
+ return defaultValue;
+ }
+ intermediateIdValue = operand.asPhi();
+ }
+ }
+ assert intermediateIdValue == null
+ || intermediateIdValue.getOperands().stream().noneMatch(Value::isPhi);
+ return intermediateIdValue != null ? intermediateIdValue : defaultValue;
+ }
+
// Attempts to build a mapping from strings to their ids starting from the given block. The
// mapping is built by traversing the control flow graph upwards, so the given block is
// expected to be the last block in the sequence of blocks that compare the hash code of the
@@ -569,17 +597,17 @@
InstructionIterator instructionIterator = block.iterator();
ConstNumber constNumberInstruction;
if (block.isTrivialGoto()) {
- if (block.getUniqueNormalSuccessor() != idValue.getBlock()) {
+ if (block.getUniqueNormalSuccessor() != intermediateIdValue.getBlock()) {
return false;
}
- int predecessorIndex = idValue.getBlock().getPredecessors().indexOf(block);
+ int predecessorIndex = intermediateIdValue.getBlock().getPredecessors().indexOf(block);
constNumberInstruction =
- asConstNumberOrNull(idValue.getOperand(predecessorIndex).definition);
+ asConstNumberOrNull(intermediateIdValue.getOperand(predecessorIndex).definition);
} else {
constNumberInstruction = instructionIterator.next().asConstNumber();
}
if (constNumberInstruction == null
- || !idValue.getOperands().contains(constNumberInstruction.outValue())) {
+ || !intermediateIdValue.getOperands().contains(constNumberInstruction.outValue())) {
return false;
}
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 cc28692..3eada1f 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
@@ -87,8 +87,11 @@
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
- return instruction.isInvoke()
- && getMethodProviderOrNull(instruction.asInvoke().getMethod()) != null;
+ return instruction.isInvoke() && methodIsBackport(instruction.asInvoke().getMethod());
+ }
+
+ public boolean methodIsBackport(DexMethod method) {
+ return getMethodProviderOrNull(method) != null;
}
public static List<DexMethod> generateListOfBackportedMethods(
@@ -165,19 +168,19 @@
DexItemFactory factory = options.itemFactory;
- if (options.minApiLevel < AndroidApiLevel.K.getLevel()) {
+ if (options.minApiLevel.isLessThan(AndroidApiLevel.K)) {
initializeAndroidKMethodProviders(factory);
}
- if (options.minApiLevel < AndroidApiLevel.N.getLevel()) {
+ if (options.minApiLevel.isLessThan(AndroidApiLevel.N)) {
initializeAndroidNMethodProviders(factory);
}
- if (options.minApiLevel < AndroidApiLevel.O.getLevel()) {
+ if (options.minApiLevel.isLessThan(AndroidApiLevel.O)) {
initializeAndroidOMethodProviders(factory);
}
- if (options.minApiLevel < AndroidApiLevel.R.getLevel()) {
+ if (options.minApiLevel.isLessThan(AndroidApiLevel.R)) {
initializeAndroidRMethodProviders(factory);
}
- if (options.minApiLevel < AndroidApiLevel.S.getLevel()) {
+ if (options.minApiLevel.isLessThan(AndroidApiLevel.S)) {
initializeAndroidSMethodProviders(factory);
}
@@ -186,13 +189,13 @@
// libraries or natively. If Optional/Stream class is not present, we do not desugar to
// avoid confusion in error messages.
if (appView.rewritePrefix.hasRewrittenType(factory.optionalType, appView)
- || options.minApiLevel >= AndroidApiLevel.N.getLevel()) {
+ || options.minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
initializeJava9OptionalMethodProviders(factory);
initializeJava10OptionalMethodProviders(factory);
initializeJava11OptionalMethodProviders(factory);
}
if (appView.rewritePrefix.hasRewrittenType(factory.streamType, appView)
- || options.minApiLevel >= AndroidApiLevel.N.getLevel()) {
+ || options.minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
initializeStreamMethodProviders(factory);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
index 11c18d0..9d7b629 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaring.java
@@ -36,4 +36,14 @@
* <p>This should return true if-and-only-if {@link #desugarInstruction} returns non-null.
*/
boolean needsDesugaring(CfInstruction instruction, ProgramMethod context);
+
+ /**
+ * Returns true if and only if needsDesugaring() answering true implies a desugaring is needed.
+ * Some optimizations may have some heuristics, so that needsDesugaring() answers true in rare
+ * case even if no desugaring is needed.
+ */
+ // TODO(b/187913003): Fixing interface desugaring should eliminate the need for this.
+ default boolean hasPreciseNeedsDesugaring() {
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
index a0c4c64..6951b39 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CfInstructionDesugaringEventConsumer.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.desugar.backports.BackportedMethodDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialBridgeInfo;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaringEventConsumer;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.lambda.LambdaDeserializationMethodRemover;
import com.android.tools.r8.ir.desugar.lambda.LambdaDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaringEventConsumer;
@@ -40,7 +41,8 @@
LambdaDesugaringEventConsumer,
NestBasedAccessDesugaringEventConsumer,
RecordDesugaringEventConsumer,
- TwrCloseResourceDesugaringEventConsumer {
+ TwrCloseResourceDesugaringEventConsumer,
+ InterfaceMethodDesugaringEventConsumer {
public static D8CfInstructionDesugaringEventConsumer createForD8(
D8MethodProcessor methodProcessor) {
@@ -59,6 +61,17 @@
return new CfInstructionDesugaringEventConsumer() {
@Override
+ public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+ assert false;
+ }
+
+ @Override
+ public void acceptInvokeStaticInterfaceOutliningMethod(
+ ProgramMethod method, ProgramMethod context) {
+ assert false;
+ }
+
+ @Override
public void acceptRecordClass(DexProgramClass recordClass) {
assert false;
}
@@ -168,6 +181,17 @@
methodProcessor.scheduleDesugaredMethodForProcessing(closeMethod);
}
+ @Override
+ public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+ methodProcessor.scheduleDesugaredMethodForProcessing(method);
+ }
+
+ @Override
+ public void acceptInvokeStaticInterfaceOutliningMethod(
+ ProgramMethod method, ProgramMethod context) {
+ methodProcessor.scheduleDesugaredMethodForProcessing(method);
+ }
+
public List<ProgramMethod> finalizeDesugaring(
AppView<?> appView, ClassConverterResult.Builder classConverterResultBuilder) {
List<ProgramMethod> needsProcessing = new ArrayList<>();
@@ -257,6 +281,17 @@
}
@Override
+ public void acceptThrowMethod(ProgramMethod method, ProgramMethod context) {
+ assert false : "TODO(b/183998768): To be implemented";
+ }
+
+ @Override
+ public void acceptInvokeStaticInterfaceOutliningMethod(
+ ProgramMethod method, ProgramMethod context) {
+ assert false : "TODO(b/183998768): To be implemented";
+ }
+
+ @Override
public void acceptBackportedMethod(ProgramMethod backportedMethod, ProgramMethod context) {
// Intentionally empty. The backported method will be hit by the tracing in R8 as if it was
// present in the input code, and thus nothing needs to be done.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 4f05b2e..6394d39 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -190,10 +190,10 @@
&& appView.options().isDesugaredLibraryCompilation()) {
return false;
}
- return overridesLibraryMethod(method);
+ return overridesNonFinalLibraryMethod(method);
}
- private boolean overridesLibraryMethod(ProgramMethod method) {
+ private boolean overridesNonFinalLibraryMethod(ProgramMethod method) {
// We look up everywhere to see if there is a supertype/interface implementing the method...
DexProgramClass holder = method.getHolder();
WorkList<DexType> workList = WorkList.newIdentityWorkList();
@@ -225,6 +225,11 @@
if (appView.rewritePrefix.hasRewrittenType(dexClass.type, appView)) {
return false;
}
+ if (dexEncodedMethod.isFinal()) {
+ // We do not introduce overrides of final methods, in this case, the runtime always
+ // execute the default behavior in the final method.
+ return false;
+ }
foundOverrideToRewrite = true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
index 26a2c5c..0d4a470 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NonEmptyCfInstructionDesugaringCollection.java
@@ -15,13 +15,13 @@
import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.EmptyCfClassDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfClassDesugaringCollection.NonEmptyCfClassDesugaringCollection;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
+import com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.desugar.nest.NestBasedAccessDesugaring;
import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
import com.android.tools.r8.ir.desugar.twr.TwrCloseResourceInstructionDesugaring;
import com.android.tools.r8.utils.IntBox;
-import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThrowingConsumer;
@@ -43,19 +43,26 @@
NonEmptyCfInstructionDesugaringCollection(AppView<?> appView) {
this.appView = appView;
this.nestBasedAccessDesugaring = NestBasedAccessDesugaring.create(appView);
+ BackportedMethodRewriter backportedMethodRewriter = null;
+ if (appView.options().enableBackportedMethodRewriting()) {
+ backportedMethodRewriter = new BackportedMethodRewriter(appView);
+ }
+ // Place TWR before Interface desugaring to eliminate potential $closeResource interface calls.
+ if (appView.options().enableTryWithResourcesDesugaring()) {
+ desugarings.add(new TwrCloseResourceInstructionDesugaring(appView));
+ }
+ // TODO(b/183998768): Enable interface method rewriter cf to cf also in R8.
+ if (appView.options().isInterfaceMethodDesugaringEnabled()
+ && !appView.enableWholeProgramOptimizations()) {
+ desugarings.add(new InterfaceMethodRewriter(appView, backportedMethodRewriter));
+ }
desugarings.add(new LambdaInstructionDesugaring(appView));
desugarings.add(new InvokeSpecialToSelfDesugaring(appView));
desugarings.add(new InvokeToPrivateRewriter());
desugarings.add(new StringConcatInstructionDesugaring(appView));
desugarings.add(new BufferCovariantReturnTypeRewriter(appView));
- if (appView.options().enableBackportedMethodRewriting()) {
- BackportedMethodRewriter backportedMethodRewriter = new BackportedMethodRewriter(appView);
- if (backportedMethodRewriter.hasBackports()) {
- desugarings.add(backportedMethodRewriter);
- }
- }
- if (appView.options().enableTryWithResourcesDesugaring()) {
- desugarings.add(new TwrCloseResourceInstructionDesugaring(appView));
+ if (backportedMethodRewriter != null && backportedMethodRewriter.hasBackports()) {
+ desugarings.add(backportedMethodRewriter);
}
if (nestBasedAccessDesugaring != null) {
desugarings.add(nestBasedAccessDesugaring);
@@ -152,10 +159,32 @@
cfCode.setMaxLocals(maxLocalsForCode.get());
cfCode.setMaxStack(maxStackForCode.get());
} else {
- assert false : "Expected code to be desugared";
+ assert noDesugaringBecauseOfImpreciseDesugaring(method);
}
}
+ private boolean noDesugaringBecauseOfImpreciseDesugaring(ProgramMethod method) {
+ assert desugarings.stream().anyMatch(desugaring -> !desugaring.hasPreciseNeedsDesugaring())
+ : "Expected code to be desugared";
+ assert needsDesugaring(method);
+ boolean foundFalsePositive = false;
+ for (CfInstruction instruction :
+ method.getDefinition().getCode().asCfCode().getInstructions()) {
+ for (CfInstructionDesugaring impreciseDesugaring :
+ Iterables.filter(desugarings, desugaring -> !desugaring.hasPreciseNeedsDesugaring())) {
+ if (impreciseDesugaring.needsDesugaring(instruction, method)) {
+ foundFalsePositive = true;
+ }
+ }
+ for (CfInstructionDesugaring preciseDesugaring :
+ Iterables.filter(desugarings, desugaring -> desugaring.hasPreciseNeedsDesugaring())) {
+ assert !preciseDesugaring.needsDesugaring(instruction, method);
+ }
+ }
+ assert foundFalsePositive;
+ return true;
+ }
+
@Override
public CfClassDesugaringCollection createClassDesugaringCollection() {
if (recordRewriter == null) {
@@ -185,8 +214,8 @@
methodProcessingContext,
appView.dexItemFactory());
if (replacement != null) {
- assert verifyNoOtherDesugaringNeeded(
- instruction, context, methodProcessingContext, iterator);
+ assert desugaring.needsDesugaring(instruction, context);
+ assert verifyNoOtherDesugaringNeeded(instruction, context, iterator, desugaring);
return replacement;
}
}
@@ -220,26 +249,26 @@
private boolean verifyNoOtherDesugaringNeeded(
CfInstruction instruction,
ProgramMethod context,
- MethodProcessingContext methodProcessingContext,
- Iterator<CfInstructionDesugaring> iterator) {
- assert IteratorUtils.nextUntil(
- iterator,
- desugaring ->
- desugaring.desugarInstruction(
- instruction,
- requiredRegisters -> {
- assert false;
- return 0;
- },
- localStackHeight -> {
- assert false;
- },
- CfInstructionDesugaringEventConsumer.createForDesugaredCode(),
- context,
- methodProcessingContext,
- appView.dexItemFactory())
- != null)
- == null;
+ Iterator<CfInstructionDesugaring> iterator,
+ CfInstructionDesugaring appliedDesugaring) {
+ iterator.forEachRemaining(
+ desugaring -> {
+ boolean alsoApplicable = desugaring.needsDesugaring(instruction, context);
+ // TODO(b/187913003): As part of precise interface desugaring, make sure the
+ // identification is explicitly non-overlapping and remove the exceptions below.
+ assert !alsoApplicable
+ || (appliedDesugaring instanceof InterfaceMethodRewriter
+ && (desugaring instanceof InvokeToPrivateRewriter
+ || desugaring instanceof D8NestBasedAccessDesugaring))
+ || (appliedDesugaring instanceof TwrCloseResourceInstructionDesugaring
+ && desugaring instanceof InterfaceMethodRewriter)
+ : "Desugaring of "
+ + instruction
+ + " has multiple matches: "
+ + appliedDesugaring.getClass().getName()
+ + " and "
+ + desugaring.getClass().getName();
+ });
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
index 1b5e6aa..2e71826 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
@@ -305,15 +305,8 @@
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
assert !instruction.isInitClass();
- // TODO(b/179146128): This is a temporary work-around to test desugaring of records
- // without rewriting the record invoke-custom. This should be removed when the record support
- // is complete.
- if (instruction.isInvokeDynamic()
- && context.getHolder().superType == factory.recordType
- && (context.getName() == factory.toStringMethodName
- || context.getName() == factory.hashCodeMethodName
- || context.getName() == factory.equalsMethodName)) {
- return true;
+ if (instruction.isInvokeDynamic()) {
+ return needsDesugaring(instruction.asInvokeDynamic(), context);
}
if (instruction.isInvoke()) {
CfInvoke cfInvoke = instruction.asInvoke();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
new file mode 100644
index 0000000..031e142
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodDesugaringEventConsumer.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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.desugar.itf;
+
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface InterfaceMethodDesugaringEventConsumer {
+
+ void acceptThrowMethod(ProgramMethod method, ProgramMethod context);
+
+ void acceptInvokeStaticInterfaceOutliningMethod(ProgramMethod method, ProgramMethod context);
+
+ // TODO(b/183998768): Add acceptCompanionClass and acceptEmulatedInterface.
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index b0a4ffa..782ff63 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -9,23 +9,24 @@
import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
import static com.android.tools.r8.ir.code.Invoke.Type.SUPER;
import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
-import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter.getRetargetPackageAndClassPrefixDescriptor;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.TYPE_WRAPPER_SUFFIX;
import static com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer.VIVIFIED_TYPE_WRAPPER_SUFFIX;
import com.android.tools.r8.DesugarGraphConsumer;
import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.ClasspathOrLibraryClass;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexCallSite;
@@ -38,6 +39,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
@@ -48,17 +50,20 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.InvokeCustom;
-import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.InvokeStatic;
-import com.android.tools.r8.ir.code.InvokeSuper;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration;
-import com.android.tools.r8.ir.desugar.itf.DefaultMethodsHelper.Collection;
+import com.android.tools.r8.ir.desugar.FreshLocalProvider;
+import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.desugar.lambda.LambdaInstructionDesugaring;
+import com.android.tools.r8.ir.desugar.stringconcat.StringConcatInstructionDesugaring;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.MethodSynthesizerConsumer;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
@@ -78,6 +83,9 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
@@ -86,6 +94,8 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
//
@@ -114,7 +124,7 @@
// set of default interface methods missing and add them, the created methods
// forward the call to an appropriate method in interface companion class.
//
-public final class InterfaceMethodRewriter {
+public final class InterfaceMethodRewriter implements CfInstructionDesugaring {
// Public for testing.
public static final String EMULATE_LIBRARY_CLASS_NAME_SUFFIX = "$-EL";
@@ -137,6 +147,10 @@
private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
private final Predicate<DexType> shouldIgnoreFromReportsPredicate;
+
+ // This is used to filter out double desugaring on backported methods.
+ private final BackportedMethodRewriter backportedMethodRewriter;
+
/** Defines a minor variation in desugaring. */
public enum Flavor {
/** Process all application resources. */
@@ -145,10 +159,24 @@
ExcludeDexResources
}
+ // Constructor for cf to cf desugaring.
+ public InterfaceMethodRewriter(AppView<?> appView, BackportedMethodRewriter rewriter) {
+ this.appView = appView;
+ this.converter = null;
+ this.backportedMethodRewriter = rewriter;
+ this.options = appView.options();
+ this.factory = appView.dexItemFactory();
+ this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
+ this.shouldIgnoreFromReportsPredicate = getShouldIgnoreFromReportsPredicate(appView);
+ initializeEmulatedInterfaceVariables();
+ }
+
+ // Constructor for IR desugaring.
public InterfaceMethodRewriter(AppView<?> appView, IRConverter converter) {
assert converter != null;
this.appView = appView;
this.converter = converter;
+ this.backportedMethodRewriter = null;
this.options = appView.options();
this.factory = appView.dexItemFactory();
this.emulatedInterfaces = options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
@@ -232,6 +260,8 @@
}
private boolean invokeNeedsRewriting(DexMethod method, Type invokeType) {
+ // TODO(b/187913003): Refactor the implementation of needsDesugaring and desugarInstruction so
+ // that the identification is shared and thus guaranteed to be equivalent.
if (invokeType == SUPER || invokeType == STATIC || invokeType == DIRECT) {
DexClass clazz = appView.appInfo().definitionFor(method.getHolderType());
if (clazz != null && clazz.isInterface()) {
@@ -240,11 +270,257 @@
return emulatedMethods.contains(method.getName());
}
if (invokeType == VIRTUAL || invokeType == INTERFACE) {
- return defaultMethodForEmulatedDispatchOrNull(method, invokeType) != null;
+ // A virtual dispatch can target a private method, on self or on a nest mate.
+ AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
+ SingleResolutionResult resolution =
+ appInfoForDesugaring.resolveMethod(method, invokeType == INTERFACE).asSingleResolution();
+ if (resolution != null && resolution.getResolvedMethod().isPrivateMethod()) {
+ return true;
+ }
+ return defaultMethodForEmulatedDispatchOrNull(method, invokeType == INTERFACE) != null;
}
return true;
}
+ @Override
+ public boolean hasPreciseNeedsDesugaring() {
+ return false;
+ }
+
+ /**
+ * If the method is not required to be desugared, scanning is used to upgrade when required the
+ * class file version, as well as reporting missing type.
+ */
+ @Override
+ public void scan(ProgramMethod context, CfInstructionDesugaringEventConsumer eventConsumer) {
+ if (isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
+ leavingStaticInvokeToInterface(context);
+ return;
+ }
+ CfCode code = context.getDefinition().getCode().asCfCode();
+ for (CfInstruction instruction : code.getInstructions()) {
+ if (instruction.isInvokeDynamic()
+ && !LambdaInstructionDesugaring.isLambdaInvoke(instruction, context, appView)
+ && !StringConcatInstructionDesugaring.isStringConcatInvoke(
+ instruction, appView.dexItemFactory())) {
+ reportInterfaceMethodHandleCallSite(instruction.asInvokeDynamic().getCallSite(), context);
+ continue;
+ }
+ if (instruction.isInvoke()) {
+ CfInvoke cfInvoke = instruction.asInvoke();
+ if (backportedMethodRewriter.methodIsBackport(cfInvoke.getMethod())) {
+ continue;
+ }
+ if (cfInvoke.isInvokeStatic()) {
+ scanInvokeStatic(cfInvoke, context);
+ } else if (cfInvoke.isInvokeSpecial()) {
+ scanInvokeDirectOrSuper(cfInvoke, context);
+ }
+ }
+ }
+ }
+
+ private void scanInvokeDirectOrSuper(CfInvoke cfInvoke, ProgramMethod context) {
+ if (cfInvoke.isInvokeConstructor(factory)) {
+ return;
+ }
+ DexMethod invokedMethod = cfInvoke.getMethod();
+ DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
+ if (clazz == null) {
+ // NOTE: For invoke-super, this leaves unchanged those calls to undefined targets.
+ // This may lead to runtime exception but we can not report it as error since it can also be
+ // the intended behavior.
+ // For invoke-direct, this reports the missing class since we don't know if it is an
+ // interface.
+ warnMissingType(context, invokedMethod.holder);
+ }
+ }
+
+ private void scanInvokeStatic(CfInvoke cfInvoke, ProgramMethod context) {
+ DexMethod invokedMethod = cfInvoke.getMethod();
+ DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
+ if (clazz == null) {
+ // NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
+ // exception but we can not report it as error since it can also be the intended
+ // behavior.
+ if (cfInvoke.isInterface()) {
+ leavingStaticInvokeToInterface(context);
+ }
+ warnMissingType(context, invokedMethod.holder);
+ return;
+ }
+
+ if (!clazz.isInterface()) {
+ if (cfInvoke.isInterface()) {
+ leavingStaticInvokeToInterface(context);
+ }
+ return;
+ }
+
+ if (isNonDesugaredLibraryClass(clazz)) {
+ // NOTE: we intentionally don't desugar static calls into static interface
+ // methods coming from android.jar since it is only possible in case v24+
+ // version of android.jar is provided.
+ //
+ // We assume such calls are properly guarded by if-checks like
+ // 'if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.XYZ) { ... }'
+ //
+ // WARNING: This may result in incorrect code on older platforms!
+ // Retarget call to an appropriate method of companion class.
+
+ if (options.canLeaveStaticInterfaceMethodInvokes()) {
+ // When leaving static interface method invokes upgrade the class file version.
+ leavingStaticInvokeToInterface(context);
+ }
+ }
+ }
+
+ @Override
+ public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ if (instruction.isInvoke()) {
+ CfInvoke cfInvoke = instruction.asInvoke();
+ return needsRewriting(cfInvoke.getMethod(), cfInvoke.getInvokeType(context), context);
+ }
+ return false;
+ }
+
+ @Override
+ public Collection<CfInstruction> desugarInstruction(
+ CfInstruction instruction,
+ FreshLocalProvider freshLocalProvider,
+ LocalStackAllocator localStackAllocator,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext,
+ DexItemFactory dexItemFactory) {
+ if (!instruction.isInvoke() || isSyntheticMethodThatShouldNotBeDoubleProcessed(context)) {
+ return null;
+ }
+ CfInvoke invoke = instruction.asInvoke();
+ if (backportedMethodRewriter.methodIsBackport(invoke.getMethod())) {
+ return null;
+ }
+
+ Function<DexMethod, Collection<CfInstruction>> rewriteInvoke =
+ (newTarget) ->
+ Collections.singletonList(
+ new CfInvoke(org.objectweb.asm.Opcodes.INVOKESTATIC, newTarget, false));
+
+ Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow =
+ (resolutionResult) ->
+ rewriteInvokeToThrowCf(
+ invoke, resolutionResult, eventConsumer, context, methodProcessingContext);
+
+ if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
+ AppInfoWithClassHierarchy appInfoForDesugaring = appView.appInfoForDesugaring();
+ SingleResolutionResult resolution =
+ appInfoForDesugaring
+ .resolveMethod(invoke.getMethod(), invoke.isInterface())
+ .asSingleResolution();
+ if (resolution != null
+ && resolution.getResolvedMethod().isPrivateMethod()
+ && resolution.isAccessibleFrom(context, appInfoForDesugaring).isTrue()) {
+ return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke);
+ }
+ return rewriteInvokeInterfaceOrInvokeVirtual(
+ invoke.getMethod(), invoke.isInterface(), rewriteInvoke);
+ }
+ if (invoke.isInvokeStatic()) {
+ Consumer<ProgramMethod> staticOutliningMethodConsumer =
+ staticOutliningMethod -> {
+ synthesizedMethods.add(staticOutliningMethod);
+ eventConsumer.acceptInvokeStaticInterfaceOutliningMethod(
+ staticOutliningMethod, context);
+ };
+ return rewriteInvokeStatic(
+ invoke.getMethod(),
+ invoke.isInterface(),
+ methodProcessingContext,
+ context,
+ staticOutliningMethodConsumer,
+ rewriteInvoke,
+ rewriteToThrow);
+ }
+ assert invoke.isInvokeSpecial();
+ if (invoke.isInvokeSuper(context.getHolderType())) {
+ return rewriteInvokeSuper(invoke.getMethod(), context, rewriteInvoke, rewriteToThrow);
+ }
+ return rewriteInvokeDirect(invoke.getMethod(), context, rewriteInvoke);
+ }
+
+ private Collection<CfInstruction> rewriteInvokeToThrowCf(
+ CfInvoke invoke,
+ SingleResolutionResult resolutionResult,
+ CfInstructionDesugaringEventConsumer eventConsumer,
+ ProgramMethod context,
+ MethodProcessingContext methodProcessingContext) {
+ if (backportedMethodRewriter != null
+ && backportedMethodRewriter.methodIsBackport(invoke.getMethod())) {
+ // In Cf to Cf it is not allowed to desugar twice the same instruction, if the backported
+ // method rewriter already desugars the instruction, it takes precedence and nothing has
+ // to be done here.
+ return null;
+ }
+
+ MethodSynthesizerConsumer methodSynthesizerConsumer;
+ if (resolutionResult == null) {
+ methodSynthesizerConsumer =
+ UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+ } else if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
+ methodSynthesizerConsumer =
+ UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
+ } else {
+ assert false;
+ return null;
+ }
+
+ assert needsDesugaring(invoke, context);
+
+ // Replace the entire effect of the invoke by by call to the throwing helper:
+ // ...
+ // invoke <method> [receiver] args*
+ // =>
+ // ...
+ // (pop arg)*
+ // [pop receiver]
+ // invoke <throwing-method>
+ // pop exception result
+ // [push fake result for <method>]
+ UtilityMethodForCodeOptimizations throwMethod =
+ methodSynthesizerConsumer.synthesizeMethod(appView, methodProcessingContext);
+ ProgramMethod throwProgramMethod = throwMethod.uncheckedGetMethod();
+ eventConsumer.acceptThrowMethod(throwProgramMethod, context);
+
+ ArrayList<CfInstruction> replacement = new ArrayList<>();
+ DexTypeList parameters = invoke.getMethod().getParameters();
+ for (int i = parameters.values.length - 1; i >= 0; i--) {
+ replacement.add(
+ new CfStackInstruction(
+ parameters.get(i).isWideType()
+ ? CfStackInstruction.Opcode.Pop2
+ : CfStackInstruction.Opcode.Pop));
+ }
+ if (!invoke.isInvokeStatic()) {
+ replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
+ }
+
+ CfInvoke throwInvoke =
+ new CfInvoke(
+ org.objectweb.asm.Opcodes.INVOKESTATIC, throwProgramMethod.getReference(), false);
+ assert throwInvoke.getMethod().getReturnType().isClassType();
+ replacement.add(throwInvoke);
+ replacement.add(new CfStackInstruction(CfStackInstruction.Opcode.Pop));
+
+ DexType returnType = invoke.getMethod().getReturnType();
+ if (returnType != factory.voidType) {
+ replacement.add(
+ returnType.isPrimitiveType()
+ ? new CfConstNumber(0, ValueType.fromDexType(returnType))
+ : new CfConstNull());
+ }
+ return replacement;
+ }
+
DexType getEmulatedInterface(DexType itf) {
return emulatedInterfaces.get(itf);
}
@@ -290,44 +566,16 @@
InstructionListIterator instructions = block.listIterator(code);
while (instructions.hasNext()) {
Instruction instruction = instructions.next();
- switch (instruction.opcode()) {
- case INVOKE_CUSTOM:
- rewriteInvokeCustom(instruction.asInvokeCustom(), context);
- break;
- case INVOKE_DIRECT:
- rewriteInvokeDirect(instruction.asInvokeDirect(), instructions, context);
- break;
- case INVOKE_STATIC:
- rewriteInvokeStatic(
- instruction.asInvokeStatic(),
- code,
- blocks,
- instructions,
- affectedValues,
- blocksToRemove,
- methodProcessor,
- methodProcessingContext);
- break;
- case INVOKE_SUPER:
- rewriteInvokeSuper(
- instruction.asInvokeSuper(),
- code,
- blocks,
- instructions,
- affectedValues,
- blocksToRemove,
- methodProcessor,
- methodProcessingContext);
- break;
- case INVOKE_INTERFACE:
- case INVOKE_VIRTUAL:
- rewriteInvokeInterfaceOrInvokeVirtual(
- instruction.asInvokeMethodWithReceiver(), instructions);
- break;
- default:
- // Intentionally empty.
- break;
- }
+ rewriteMethodReferences(
+ code,
+ methodProcessor,
+ methodProcessingContext,
+ context,
+ affectedValues,
+ blocksToRemove,
+ blocks,
+ instructions,
+ instruction);
}
}
@@ -340,14 +588,71 @@
assert code.isConsistentSSA();
}
+ private void rewriteMethodReferences(
+ IRCode code,
+ MethodProcessor methodProcessor,
+ MethodProcessingContext methodProcessingContext,
+ ProgramMethod context,
+ Set<Value> affectedValues,
+ Set<BasicBlock> blocksToRemove,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator instructions,
+ Instruction instruction) {
+ if (instruction.isInvokeCustom()) {
+ reportInterfaceMethodHandleCallSite(instruction.asInvokeCustom().getCallSite(), context);
+ return;
+ }
+ if (!instruction.isInvokeMethod()) {
+ return;
+ }
+ InvokeMethod invoke = instruction.asInvokeMethod();
+ Function<DexMethod, Collection<CfInstruction>> rewriteInvoke =
+ (newTarget) -> {
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(newTarget, invoke.outValue(), invoke.arguments()));
+ return null;
+ };
+ if (instruction.isInvokeDirect()) {
+ rewriteInvokeDirect(invoke.getInvokedMethod(), context, rewriteInvoke);
+ } else if (instruction.isInvokeVirtual() || instruction.isInvokeInterface()) {
+ rewriteInvokeInterfaceOrInvokeVirtual(
+ invoke.getInvokedMethod(), invoke.getInterfaceBit(), rewriteInvoke);
+ } else {
+ Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow =
+ (resolutionResult) ->
+ rewriteInvokeToThrowIR(
+ invoke,
+ resolutionResult,
+ code,
+ blocks,
+ instructions,
+ affectedValues,
+ blocksToRemove,
+ methodProcessor,
+ methodProcessingContext);
+ if (instruction.isInvokeStatic()) {
+ rewriteInvokeStatic(
+ invoke.getInvokedMethod(),
+ invoke.getInterfaceBit(),
+ methodProcessingContext,
+ context,
+ synthesizedMethods::add,
+ rewriteInvoke,
+ rewriteToThrow);
+ } else {
+ assert instruction.isInvokeSuper();
+ rewriteInvokeSuper(invoke.getInvokedMethod(), context, rewriteInvoke, rewriteToThrow);
+ }
+ }
+ }
+
private boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
return appView.getSyntheticItems().isSyntheticMethodThatShouldNotBeDoubleProcessed(method);
}
- private void rewriteInvokeCustom(InvokeCustom invoke, ProgramMethod context) {
+ private void reportInterfaceMethodHandleCallSite(DexCallSite callSite, ProgramMethod context) {
// Check that static interface methods are not referenced from invoke-custom instructions via
// method handles.
- DexCallSite callSite = invoke.getCallSite();
reportStaticInterfaceMethodHandle(context, callSite.bootstrapMethod);
for (DexValue arg : callSite.bootstrapArgs) {
if (arg.isDexValueMethodHandle()) {
@@ -356,22 +661,23 @@
}
}
- private void rewriteInvokeDirect(
- InvokeDirect invoke, InstructionListIterator instructions, ProgramMethod context) {
- DexMethod method = invoke.getInvokedMethod();
- if (factory.isConstructor(method)) {
- return;
+ private Collection<CfInstruction> rewriteInvokeDirect(
+ DexMethod invokedMethod,
+ ProgramMethod context,
+ Function<DexMethod, Collection<CfInstruction>> rewriteInvoke) {
+ if (factory.isConstructor(invokedMethod)) {
+ return null;
}
- DexClass clazz = appView.definitionForHolder(method, context);
+ DexClass clazz = appView.definitionForHolder(invokedMethod, context);
if (clazz == null) {
// Report missing class since we don't know if it is an interface.
- warnMissingType(context, method.holder);
- return;
+ warnMissingType(context, invokedMethod.holder);
+ return null;
}
if (!clazz.isInterface()) {
- return;
+ return null;
}
if (clazz.isLibraryClass()) {
@@ -382,72 +688,63 @@
getMethodOrigin(context.getReference()));
}
- DexClassAndMethod directTarget = clazz.lookupClassMethod(method);
+ DexClassAndMethod directTarget = clazz.lookupClassMethod(invokedMethod);
if (directTarget != null) {
// This can be a private instance method call. Note that the referenced
// method is expected to be in the current class since it is private, but desugaring
// may move some methods or their code into other classes.
- assert invokeNeedsRewriting(method, DIRECT);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- directTarget.getDefinition().isPrivateMethod()
- ? privateAsMethodOfCompanionClass(directTarget)
- : defaultAsMethodOfCompanionClass(directTarget),
- invoke.outValue(),
- invoke.arguments()));
+ assert invokeNeedsRewriting(invokedMethod, DIRECT);
+ return rewriteInvoke.apply(
+ directTarget.getDefinition().isPrivateMethod()
+ ? privateAsMethodOfCompanionClass(directTarget)
+ : defaultAsMethodOfCompanionClass(directTarget));
} else {
// The method can be a default method in the interface hierarchy.
DexClassAndMethod virtualTarget =
- appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, method);
+ appView.appInfoForDesugaring().lookupMaximallySpecificMethod(clazz, invokedMethod);
if (virtualTarget != null) {
// This is a invoke-direct call to a virtual method.
- assert invokeNeedsRewriting(method, DIRECT);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- defaultAsMethodOfCompanionClass(virtualTarget),
- invoke.outValue(),
- invoke.arguments()));
+ assert invokeNeedsRewriting(invokedMethod, DIRECT);
+ return rewriteInvoke.apply(defaultAsMethodOfCompanionClass(virtualTarget));
} else {
// The below assert is here because a well-type program should have a target, but we
// cannot throw a compilation error, since we have no knowledge about the input.
assert false;
}
}
+ return null;
}
- private void rewriteInvokeStatic(
- InvokeStatic invoke,
- IRCode code,
- ListIterator<BasicBlock> blockIterator,
- InstructionListIterator instructions,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove,
- MethodProcessor methodProcessor,
- MethodProcessingContext methodProcessingContext) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
+ private Collection<CfInstruction> rewriteInvokeStatic(
+ DexMethod invokedMethod,
+ boolean interfaceBit,
+ MethodProcessingContext methodProcessingContext,
+ ProgramMethod context,
+ Consumer<ProgramMethod> staticOutliningMethodConsumer,
+ Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
+ Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow) {
if (appView.getSyntheticItems().isPendingSynthetic(invokedMethod.holder)) {
// We did not create this code yet, but it will not require rewriting.
- return;
+ return null;
}
- ProgramMethod context = code.context();
DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
if (clazz == null) {
// NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
// exception but we can not report it as error since it can also be the intended
// behavior.
- if (invoke.getInterfaceBit()) {
+ if (interfaceBit) {
leavingStaticInvokeToInterface(context);
}
warnMissingType(context, invokedMethod.holder);
- return;
+ return null;
}
if (!clazz.isInterface()) {
- if (invoke.getInterfaceBit()) {
+ if (interfaceBit) {
leavingStaticInvokeToInterface(context);
}
- return;
+ return null;
}
if (isNonDesugaredLibraryClass(clazz)) {
@@ -467,6 +764,19 @@
// so the user class is not rejected because it make this call directly.
// TODO(b/166247515): If this an incorrect invoke-static without the interface bit
// we end up "fixing" the code and remove and ICCE error.
+ if (synthesizedMethods.contains(context)) {
+ // When reprocessing the method generated below, the desugaring asserts this method
+ // does not need any new desugaring, while the interface method rewriter tries
+ // to outline again the invoke-static. Just do nothing instead.
+ return null;
+ }
+ if (backportedMethodRewriter != null
+ && backportedMethodRewriter.methodIsBackport(invokedMethod)) {
+ // In Cf to Cf it is not allowed to desugar twice the same instruction, if the backported
+ // method rewriter already desugars the instruction, it takes precedence and nothing has
+ // to be done here.
+ return null;
+ }
ProgramMethod newProgramMethod =
appView
.getSyntheticItems()
@@ -484,19 +794,17 @@
.setStaticTarget(invokedMethod, true)
.setStaticSource(m)
.build()));
+ staticOutliningMethodConsumer.accept(newProgramMethod);
assert invokeNeedsRewriting(invokedMethod, STATIC);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- newProgramMethod.getReference(), invoke.outValue(), invoke.arguments()));
// The synthetic dispatch class has static interface method invokes, so set
// the class file version accordingly.
- newProgramMethod.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
- synthesizedMethods.add(newProgramMethod);
+ leavingStaticInvokeToInterface(newProgramMethod);
+ return rewriteInvoke.apply(newProgramMethod.getReference());
} else {
// When leaving static interface method invokes upgrade the class file version.
- context.getDefinition().upgradeClassFileVersion(CfVersion.V1_8);
+ leavingStaticInvokeToInterface(context);
}
- return;
+ return null;
}
SingleResolutionResult resolutionResult =
@@ -504,67 +812,38 @@
.appInfoForDesugaring()
.resolveMethodOnInterface(clazz, invokedMethod)
.asSingleResolution();
- if (clazz.isInterface()
- && rewriteInvokeToThrow(
- invoke,
- resolutionResult,
- code,
- blockIterator,
- instructions,
- affectedValues,
- blocksToRemove,
- methodProcessor,
- methodProcessingContext)) {
- assert invokeNeedsRewriting(invoke.getInvokedMethod(), STATIC);
- return;
+ if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, true)) {
+ assert invokeNeedsRewriting(invokedMethod, STATIC);
+ return rewriteToThrow.apply(resolutionResult);
}
assert resolutionResult != null;
assert resolutionResult.getResolvedMethod().isStatic();
assert invokeNeedsRewriting(invokedMethod, STATIC);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()),
- invoke.outValue(),
- invoke.arguments()));
+ return rewriteInvoke.apply(
+ staticAsMethodOfCompanionClass(resolutionResult.getResolutionPair()));
}
- private void rewriteInvokeSuper(
- InvokeSuper invoke,
- IRCode code,
- ListIterator<BasicBlock> blockIterator,
- InstructionListIterator instructions,
- Set<Value> affectedValues,
- Set<BasicBlock> blocksToRemove,
- MethodProcessor methodProcessor,
- MethodProcessingContext methodProcessingContext) {
- ProgramMethod context = code.context();
- DexMethod invokedMethod = invoke.getInvokedMethod();
+ private Collection<CfInstruction> rewriteInvokeSuper(
+ DexMethod invokedMethod,
+ ProgramMethod context,
+ Function<DexMethod, Collection<CfInstruction>> rewriteInvoke,
+ Function<SingleResolutionResult, Collection<CfInstruction>> rewriteToThrow) {
DexClass clazz = appView.definitionFor(invokedMethod.holder, context);
if (clazz == null) {
// NOTE: leave unchanged those calls to undefined targets. This may lead to runtime
// exception but we can not report it as error since it can also be the intended
// behavior.
warnMissingType(context, invokedMethod.holder);
- return;
+ return null;
}
SingleResolutionResult resolutionResult =
appView.appInfoForDesugaring().resolveMethodOn(clazz, invokedMethod).asSingleResolution();
- if (clazz.isInterface()
- && rewriteInvokeToThrow(
- invoke,
- resolutionResult,
- code,
- blockIterator,
- instructions,
- affectedValues,
- blocksToRemove,
- methodProcessor,
- methodProcessingContext)) {
- assert invokeNeedsRewriting(invoke.getInvokedMethod(), SUPER);
- return;
+ if (clazz.isInterface() && shouldRewriteToInvokeToThrow(resolutionResult, false)) {
+ assert invokeNeedsRewriting(invokedMethod, SUPER);
+ return rewriteToThrow.apply(resolutionResult);
}
if (clazz.isInterface() && !clazz.isLibraryClass()) {
@@ -578,81 +857,83 @@
// WARNING: This may result in incorrect code on older platforms!
// Retarget call to an appropriate method of companion class.
assert invokeNeedsRewriting(invokedMethod, SUPER);
- DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- defaultAsMethodOfCompanionClass(amendedMethod, appView.dexItemFactory()),
- invoke.outValue(),
- invoke.arguments()));
- } else {
- DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
- if (emulatedItf == null) {
- if (clazz.isInterface() && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
- DexClassAndMethod target =
- appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
- if (target != null && target.getDefinition().isDefaultMethod()) {
- DexClass holder = target.getHolder();
- if (holder.isLibraryClass() && holder.isInterface()) {
- assert invokeNeedsRewriting(invokedMethod, SUPER);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- defaultAsMethodOfCompanionClass(target),
- invoke.outValue(),
- invoke.arguments()));
- }
- }
+ if (resolutionResult.getResolvedMethod().isPrivateMethod()) {
+ if (resolutionResult.isAccessibleFrom(context, appView.appInfoForDesugaring()).isFalse()) {
+ // TODO(b/145775365): This should throw IAE.
+ return rewriteToThrow.apply(null);
}
+ return rewriteInvoke.apply(
+ privateAsMethodOfCompanionClass(resolutionResult.getResolutionPair()));
} else {
- // That invoke super may not resolve since the super method may not be present
- // since it's in the emulated interface. We need to force resolution. If it resolves
- // to a library method, then it needs to be rewritten.
- // If it resolves to a program overrides, the invoke-super can remain.
- DexClassAndMethod superTarget =
- appView.appInfoForDesugaring().lookupSuperTarget(invoke.getInvokedMethod(), context);
- if (superTarget != null && superTarget.isLibraryMethod()) {
- // Rewriting is required because the super invoke resolves into a missing
- // method (method is on desugared library). Find out if it needs to be
- // retarget or if it just calls a companion class method and rewrite.
- DexMethod retargetMethod =
- options.desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
- if (retargetMethod == null) {
- DexMethod originalCompanionMethod = defaultAsMethodOfCompanionClass(superTarget);
- DexMethod companionMethod =
- factory.createMethod(
- getCompanionClassType(emulatedItf),
- factory.protoWithDifferentFirstParameter(
- originalCompanionMethod.proto, emulatedItf),
- originalCompanionMethod.name);
+ DexMethod amendedMethod = amendDefaultMethod(context.getHolder(), invokedMethod);
+ return rewriteInvoke.apply(
+ defaultAsMethodOfCompanionClass(amendedMethod, appView.dexItemFactory()));
+ }
+ }
+
+ DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
+ if (emulatedItf == null) {
+ if (clazz.isInterface() && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
+ DexClassAndMethod target =
+ appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
+ if (target != null && target.getDefinition().isDefaultMethod()) {
+ DexClass holder = target.getHolder();
+ if (holder.isLibraryClass() && holder.isInterface()) {
assert invokeNeedsRewriting(invokedMethod, SUPER);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(companionMethod, invoke.outValue(), invoke.arguments()));
- } else {
- assert invokeNeedsRewriting(invokedMethod, SUPER);
- instructions.replaceCurrentInstruction(
- new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
+ return rewriteInvoke.apply(defaultAsMethodOfCompanionClass(target));
}
}
}
+ return null;
}
+ // That invoke super may not resolve since the super method may not be present
+ // since it's in the emulated interface. We need to force resolution. If it resolves
+ // to a library method, then it needs to be rewritten.
+ // If it resolves to a program overrides, the invoke-super can remain.
+ DexClassAndMethod superTarget =
+ appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, context);
+ if (superTarget != null && superTarget.isLibraryMethod()) {
+ // Rewriting is required because the super invoke resolves into a missing
+ // method (method is on desugared library). Find out if it needs to be
+ // retarget or if it just calls a companion class method and rewrite.
+ DexMethod retargetMethod =
+ options.desugaredLibraryConfiguration.retargetMethod(superTarget, appView);
+ if (retargetMethod == null) {
+ DexMethod originalCompanionMethod = defaultAsMethodOfCompanionClass(superTarget);
+ DexMethod companionMethod =
+ factory.createMethod(
+ getCompanionClassType(emulatedItf),
+ factory.protoWithDifferentFirstParameter(
+ originalCompanionMethod.proto, emulatedItf),
+ originalCompanionMethod.name);
+ assert invokeNeedsRewriting(invokedMethod, SUPER);
+ return rewriteInvoke.apply(companionMethod);
+ } else {
+ assert invokeNeedsRewriting(invokedMethod, SUPER);
+ return rewriteInvoke.apply(retargetMethod);
+ }
+ }
+ return null;
}
private DexClassAndMethod defaultMethodForEmulatedDispatchOrNull(
- DexMethod method, Type invokeType) {
- assert invokeType == VIRTUAL || invokeType == INTERFACE;
- boolean interfaceBit = invokeType.isInterface();
- DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(method);
+ DexMethod invokedMethod, boolean interfaceBit) {
+ DexType emulatedItf = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
if (emulatedItf == null) {
return null;
}
// The call potentially ends up in a library class, in which case we need to rewrite, since the
// code may be in the desugared library.
SingleResolutionResult resolution =
- appView.appInfoForDesugaring().resolveMethod(method, interfaceBit).asSingleResolution();
+ appView
+ .appInfoForDesugaring()
+ .resolveMethod(invokedMethod, interfaceBit)
+ .asSingleResolution();
if (resolution != null
&& (resolution.getResolvedHolder().isLibraryClass()
|| appView.options().isDesugaredLibraryCompilation())) {
DexClassAndMethod defaultMethod =
- appView.definitionFor(emulatedItf).lookupClassMethod(method);
+ appView.definitionFor(emulatedItf).lookupClassMethod(invokedMethod);
if (defaultMethod != null && !dontRewrite(defaultMethod)) {
assert !defaultMethod.getAccessFlags().isAbstract();
return defaultMethod;
@@ -661,20 +942,25 @@
return null;
}
- private void rewriteInvokeInterfaceOrInvokeVirtual(
- InvokeMethodWithReceiver invoke, InstructionListIterator instructions) {
+ private Collection<CfInstruction> rewriteInvokeInterfaceOrInvokeVirtual(
+ DexMethod invokedMethod,
+ boolean interfaceBit,
+ Function<DexMethod, Collection<CfInstruction>> rewriteInvoke) {
DexClassAndMethod defaultMethod =
- defaultMethodForEmulatedDispatchOrNull(invoke.getInvokedMethod(), invoke.getType());
+ defaultMethodForEmulatedDispatchOrNull(invokedMethod, interfaceBit);
if (defaultMethod != null) {
- instructions.replaceCurrentInstruction(
- new InvokeStatic(
- emulateInterfaceLibraryMethod(defaultMethod, factory),
- invoke.outValue(),
- invoke.arguments()));
+ return rewriteInvoke.apply(emulateInterfaceLibraryMethod(defaultMethod, factory));
}
+ return null;
}
- private boolean rewriteInvokeToThrow(
+ private boolean shouldRewriteToInvokeToThrow(
+ SingleResolutionResult resolutionResult, boolean isInvokeStatic) {
+ return resolutionResult == null
+ || resolutionResult.getResolvedMethod().isStatic() != isInvokeStatic;
+ }
+
+ private Collection<CfInstruction> rewriteInvokeToThrowIR(
InvokeMethod invoke,
SingleResolutionResult resolutionResult,
IRCode code,
@@ -692,7 +978,8 @@
methodSynthesizerConsumer =
UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
} else {
- return false;
+ assert false;
+ return null;
}
// Replace by throw new SomeException.
@@ -725,7 +1012,7 @@
throwBlockIterator.next();
throwBlockIterator.replaceCurrentInstructionWithThrow(
appView, code, blockIterator, throwInvoke.outValue(), blocksToRemove, affectedValues);
- return true;
+ return null;
}
private DexType maximallySpecificEmulatedInterfaceOrNull(DexMethod invokedMethod) {
@@ -1100,7 +1387,7 @@
return collection;
}
collection = createInterfaceInfo(classToDesugar, implementing, iface);
- Collection existing = cache.putIfAbsent(iface, collection);
+ DefaultMethodsHelper.Collection existing = cache.putIfAbsent(iface, collection);
return existing != null ? existing : collection;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index b0df7c7..bd95f96 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -30,7 +30,9 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue.DexValueInt;
import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.MethodCollection;
@@ -70,10 +72,12 @@
private final InterfaceMethodRewriter rewriter;
private final Map<DexProgramClass, PostProcessingInterfaceInfo> postProcessingInterfaceInfos =
new ConcurrentHashMap<>();
+ private final ClassTypeSignature objectTypeSignature;
InterfaceProcessor(AppView<?> appView, InterfaceMethodRewriter rewriter) {
this.appView = appView;
this.rewriter = rewriter;
+ this.objectTypeSignature = new ClassTypeSignature(appView.dexItemFactory().objectType);
}
@Override
@@ -129,6 +133,8 @@
appView,
builder -> {
builder.setSourceFile(iface.sourceFile);
+ builder.setGenericSignature(
+ iface.getClassSignature().toObjectBoundWithSameFormals(objectTypeSignature));
ensureCompanionClassInitializesInterface(iface, builder);
processVirtualInterfaceMethods(iface, builder);
processDirectInterfaceMethods(iface, builder);
@@ -260,7 +266,7 @@
.setName(companionMethod.getName())
.setProto(companionMethod.getProto())
.setAccessFlags(newFlags)
- .setGenericSignature(virtual.getGenericSignature())
+ .setGenericSignature(MethodTypeSignature.noSignature())
.setAnnotations(virtual.annotations())
.setParameterAnnotationsList(virtual.getParameterAnnotations())
.setCode(ignored -> virtual.getCode())
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index ad1cacb..71010c3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -158,6 +158,11 @@
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
+ return isLambdaInvoke(instruction, context, appView);
+ }
+
+ public static boolean isLambdaInvoke(
+ CfInstruction instruction, ProgramMethod context, AppView<?> appView) {
return instruction.isInvokeDynamic()
&& LambdaDescriptor.tryInfer(
instruction.asInvokeDynamic().getCallSite(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
index 4f4d963..607b088 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/stringconcat/StringConcatInstructionDesugaring.java
@@ -216,13 +216,17 @@
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
- return instruction.isInvokeDynamic()
- && needsDesugaring(instruction.asInvokeDynamic().getCallSite());
+ return isStringConcatInvoke(instruction, factory);
}
- private boolean needsDesugaring(DexCallSite callSite) {
+ public static boolean isStringConcatInvoke(CfInstruction instruction, DexItemFactory factory) {
+ CfInvokeDynamic invoke = instruction.asInvokeDynamic();
+ if (invoke == null) {
+ return false;
+ }
// We are interested in bootstrap methods StringConcatFactory::makeConcat
// and StringConcatFactory::makeConcatWithConstants, both are static.
+ DexCallSite callSite = invoke.getCallSite();
if (callSite.bootstrapMethod.type.isInvokeStatic()) {
DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
return bootstrapMethod == factory.stringConcatFactoryMembers.makeConcat
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 8e66601..ececbe3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import static com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo.isApiSafeForInlining;
import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.addMonitorEnterValue;
import static com.android.tools.r8.ir.optimize.inliner.InlinerUtils.collectAllMonitorEnterValues;
@@ -33,6 +34,7 @@
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
@@ -121,11 +123,22 @@
Reason reason,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
- if (singleTargetMethod.getOptimizationInfo().neverInline()) {
+ MethodOptimizationInfo targetOptimizationInfo = singleTargetMethod.getOptimizationInfo();
+ if (targetOptimizationInfo.neverInline()) {
whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
return false;
}
+ // Do not inline if the inlinee is greater than the api caller level.
+ MethodOptimizationInfo callerOptimizationInfo = method.getDefinition().getOptimizationInfo();
+ // TODO(b/188498051): We should not force inline lower api method calls.
+ if (reason != Reason.FORCE
+ && isApiSafeForInlining(callerOptimizationInfo, targetOptimizationInfo, appView.options())
+ .isPossiblyFalse()) {
+ whyAreYouNotInliningReporter.reportInlineeHigherApiCall();
+ return false;
+ }
+
// We don't inline into constructors when producing class files since this can mess up
// the stackmap, see b/136250031
if (method.getDefinition().isInstanceInitializer()
@@ -138,7 +151,7 @@
if (method.getDefinition() == singleTargetMethod) {
// Cannot handle recursive inlining at this point.
// Force inlined method should never be recursive.
- assert !singleTargetMethod.getOptimizationInfo().forceInline();
+ assert !targetOptimizationInfo.forceInline();
whyAreYouNotInliningReporter.reportRecursiveMethod();
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index 953a78c..2b15739 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -177,6 +177,10 @@
return method;
}
+ public ProgramMethod uncheckedGetMethod() {
+ return method;
+ }
+
public void optimize(MethodProcessor methodProcessor) {
methodProcessor.scheduleDesugaredMethodForProcessing(method);
optimized = true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index aacfec7..5f6ad1a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -505,7 +505,15 @@
method, code, methodCallsOnInstance, inliningIRProvider, Timing.empty());
} else {
assert indirectMethodCallsOnInstance.stream()
- .noneMatch(method -> method.getDefinition().getOptimizationInfo().mayHaveSideEffects());
+ .filter(method -> method.getDefinition().getOptimizationInfo().mayHaveSideEffects())
+ .allMatch(
+ method ->
+ method.getDefinition().isInstanceInitializer()
+ && !method
+ .getDefinition()
+ .getOptimizationInfo()
+ .getContextInsensitiveInstanceInitializerInfo()
+ .mayHaveOtherSideEffectsThanInstanceFieldAssignments());
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index d49189f..2b7ef1a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.DefaultInstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableSet;
import java.util.BitSet;
import java.util.Set;
@@ -38,8 +39,9 @@
static boolean UNKNOWN_RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS = false;
static BitSet NO_NULL_PARAMETER_OR_THROW_FACTS = null;
static BitSet NO_NULL_PARAMETER_ON_NORMAL_EXITS_FACTS = null;
+ static AndroidApiLevel UNKNOWN_API_REFERENCE_LEVEL = null;
- private DefaultMethodOptimizationInfo() {}
+ protected DefaultMethodOptimizationInfo() {}
public static DefaultMethodOptimizationInfo getInstance() {
return DEFAULT_INSTANCE;
@@ -57,7 +59,7 @@
@Override
public UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo() {
- return null;
+ return mutableCopy();
}
@Override
@@ -192,6 +194,16 @@
}
@Override
+ public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+ return UNKNOWN_API_REFERENCE_LEVEL;
+ }
+
+ @Override
+ public boolean hasApiReferenceLevel() {
+ return false;
+ }
+
+ @Override
public UpdatableMethodOptimizationInfo mutableCopy() {
return new UpdatableMethodOptimizationInfo();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
new file mode 100644
index 0000000..fc61ba6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationWithMinApiInfo.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2021, 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.utils.AndroidApiLevel;
+
+public class DefaultMethodOptimizationWithMinApiInfo extends DefaultMethodOptimizationInfo {
+
+ private static final DefaultMethodOptimizationWithMinApiInfo DEFAULT_MIN_API_INSTANCE =
+ new DefaultMethodOptimizationWithMinApiInfo();
+
+ public static DefaultMethodOptimizationWithMinApiInfo getInstance() {
+ return DEFAULT_MIN_API_INSTANCE;
+ }
+
+ @Override
+ public boolean hasApiReferenceLevel() {
+ return true;
+ }
+
+ @Override
+ public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+ return minApi;
+ }
+
+ @Override
+ public UpdatableMethodOptimizationInfo mutableCopy() {
+ UpdatableMethodOptimizationInfo updatableMethodOptimizationInfo = super.mutableCopy();
+ // Use null to specify that the min api is set to minApi.
+ updatableMethodOptimizationInfo.setApiReferenceLevel(null);
+ return updatableMethodOptimizationInfo;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index bdc35f3..0190cd0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.info;
+import static com.android.tools.r8.utils.OptionalBool.UNKNOWN;
+
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
@@ -13,6 +15,9 @@
import com.android.tools.r8.ir.optimize.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OptionalBool;
import java.util.BitSet;
import java.util.Set;
@@ -95,5 +100,23 @@
public abstract boolean returnValueHasBeenPropagated();
+ public abstract AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi);
+
+ public abstract boolean hasApiReferenceLevel();
+
public abstract UpdatableMethodOptimizationInfo mutableCopy();
+
+ public static OptionalBool isApiSafeForInlining(
+ MethodOptimizationInfo caller, MethodOptimizationInfo inlinee, InternalOptions options) {
+ if (!options.apiModelingOptions().enableApiCallerIdentification) {
+ return OptionalBool.TRUE;
+ }
+ if (!caller.hasApiReferenceLevel() || !inlinee.hasApiReferenceLevel()) {
+ return UNKNOWN;
+ }
+ return OptionalBool.of(
+ caller
+ .getApiReferenceLevel(options.minApiLevel)
+ .isGreaterThanOrEqualTo(inlinee.getApiReferenceLevel(options.minApiLevel)));
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 436f996..15afa8c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -73,6 +73,7 @@
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Instruction.SideEffectAssumption;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
@@ -370,7 +371,8 @@
if (singleTarget == null) {
return null;
}
- if (singleTarget.isInstanceInitializer() && invoke.getReceiver() == receiver) {
+ if (singleTarget.isInstanceInitializer()
+ && invoke.getReceiver().getAliasedValue() == receiver) {
if (builder.hasParent() && builder.getParent() != singleTarget.getReference()) {
return null;
}
@@ -927,7 +929,18 @@
mayHaveSideEffects = false;
// Otherwise, check if there is an instruction that has side effects.
for (Instruction instruction : code.instructions()) {
- if (instruction.instructionMayHaveSideEffects(appView, context)) {
+ if (instruction.isInvokeConstructor(appView.dexItemFactory())
+ && instruction
+ .asInvokeDirect()
+ .getReceiver()
+ .getAliasedValue()
+ .isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
+ if (instruction.instructionMayHaveSideEffects(
+ appView, context, SideEffectAssumption.IGNORE_RECEIVER_FIELD_ASSIGNMENTS)) {
+ mayHaveSideEffects = true;
+ break;
+ }
+ } else if (instruction.instructionMayHaveSideEffects(appView, context)) {
mayHaveSideEffects = true;
break;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 0d2e43b..85e2576 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -22,8 +22,10 @@
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfoCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import java.util.BitSet;
+import java.util.Optional;
import java.util.Set;
public class UpdatableMethodOptimizationInfo extends MethodOptimizationInfo {
@@ -66,6 +68,8 @@
private SimpleInliningConstraint simpleInliningConstraint =
NeverSimpleInliningConstraint.getInstance();
+ private Optional<AndroidApiLevel> apiReferenceLevel = null;
+
// To reduce the memory footprint of UpdatableMethodOptimizationInfo, all the boolean fields are
// merged into a flag int field. The various static final FLAG fields indicate which bit is
// used by each boolean. DEFAULT_FLAGS encodes the default value for efficient instantiation and
@@ -146,6 +150,7 @@
nonNullParamOrThrow = template.nonNullParamOrThrow;
nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
classInlinerConstraint = template.classInlinerConstraint;
+ apiReferenceLevel = template.apiReferenceLevel;
}
public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
@@ -495,6 +500,23 @@
}
@Override
+ public AndroidApiLevel getApiReferenceLevel(AndroidApiLevel minApi) {
+ assert hasApiReferenceLevel();
+ return apiReferenceLevel.orElse(minApi);
+ }
+
+ @SuppressWarnings("OptionalAssignedToNull")
+ @Override
+ public boolean hasApiReferenceLevel() {
+ return apiReferenceLevel != null;
+ }
+
+ public UpdatableMethodOptimizationInfo setApiReferenceLevel(AndroidApiLevel apiReferenceLevel) {
+ this.apiReferenceLevel = Optional.ofNullable(apiReferenceLevel);
+ return this;
+ }
+
+ @Override
public UpdatableMethodOptimizationInfo mutableCopy() {
return new UpdatableMethodOptimizationInfo(this);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
index af59b7c..e976680 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/DefaultInstanceInitializerInfo.java
@@ -30,6 +30,11 @@
}
@Override
+ public boolean hasParent() {
+ return false;
+ }
+
+ @Override
public DexMethod getParent() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
index 3865bc1..27576e9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/InstanceInitializerInfo.java
@@ -25,6 +25,8 @@
return null;
}
+ public abstract boolean hasParent();
+
public abstract DexMethod getParent();
public abstract InstanceFieldInitializationInfoCollection fieldInitializationInfos();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
index 40b3edc..9dd2750 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/initializer/NonTrivialInstanceInitializerInfo.java
@@ -63,6 +63,11 @@
}
@Override
+ public boolean hasParent() {
+ return parent != null;
+ }
+
+ @Override
public DexMethod getParent() {
return parent;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index baf75b5..dba0cde 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.origin.Origin;
import java.util.IdentityHashMap;
@@ -40,7 +41,12 @@
Position position = Position.getPositionForInlining(appView, invoke, context);
Origin origin = method.getOrigin();
return method.buildInliningIR(
- context, appView, valueNumberGenerator, position, origin, methodProcessor);
+ context,
+ appView,
+ valueNumberGenerator,
+ position,
+ origin,
+ IRBuilder.lookupPrototypeChangesForInlinee(appView, method, methodProcessor));
}
public IRCode getAndCacheInliningIR(InvokeMethod invoke, ProgramMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
index e398dce..218a8fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/NopWhyAreYouNotInliningReporter.java
@@ -58,6 +58,9 @@
public void reportInlineeNotSimple() {}
@Override
+ public void reportInlineeHigherApiCall() {}
+
+ @Override
public void reportInlineeRefersToClassesNotInMainDex() {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 81c6a35..8063674 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -69,6 +69,8 @@
public abstract void reportInlineeNotSimple();
+ public abstract void reportInlineeHigherApiCall();
+
public abstract void reportInlineeRefersToClassesNotInMainDex();
public abstract void reportInliningAcrossFeatureSplit();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 257cc15..f9418b4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -120,6 +120,11 @@
}
@Override
+ public void reportInlineeHigherApiCall() {
+ print("inlinee having a higher api call than caller context.");
+ }
+
+ @Override
public void reportInlineeRefersToClassesNotInMainDex() {
print(
"inlining could increase the main dex size "
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 69d65c8..b809e23 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -391,7 +391,7 @@
IRCode code = method.buildIR(appView);
codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code, methodProcessor));
CodeRewriter.removeAssumeInstructions(appView, code);
- converter.removeDeadCodeAndFinalizeIR(method, code, feedback, Timing.empty());
+ converter.removeDeadCodeAndFinalizeIR(code, feedback, Timing.empty());
}
private void insertAssumeInstructions(IRCode code, MethodProcessor methodProcessor) {
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
index 4dc56de..2c1b9a3 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/AbstractSynthesizedCode.java
@@ -11,12 +11,12 @@
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.NumberGenerator;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.SourceCode;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.origin.Origin;
@@ -51,14 +51,14 @@
NumberGenerator valueNumberGenerator,
Position callerPosition,
Origin origin,
- MethodProcessor methodProcessor) {
+ RewrittenPrototypeDescription protoChanges) {
return IRBuilder.createForInlining(
method,
appView,
getSourceCodeProvider().get(context, callerPosition),
origin,
- methodProcessor,
- valueNumberGenerator)
+ valueNumberGenerator,
+ protoChanges)
.build(context);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index 6422c69..fc56e4e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -100,7 +101,20 @@
}
appView.setCfByteCodePassThrough(keepByteCodeFunctions);
} else {
- assert verifyKotlinMetadataModeledForAllClasses(enqueuer, keepMetadata);
+ assert enqueuer.getMode().isFinalTreeShaking();
+ enqueuer.forAllLiveClasses(
+ clazz -> {
+ if (!enqueuer.isPinned(clazz.getType())) {
+ clazz.setKotlinInfo(getNoKotlinInfo());
+ clazz.members().forEach(DexEncodedMember::clearKotlinInfo);
+ clazz.removeAnnotations(
+ annotation -> annotation.getAnnotationType() == kotlinMetadataType);
+ } else {
+ assert !hasKotlinClassMetadataAnnotation(clazz, definitionsForContext(clazz))
+ || !keepMetadata
+ || clazz.getKotlinInfo() != getNoKotlinInfo();
+ }
+ });
}
// Trace through the modeled kotlin metadata.
enqueuer.forAllLiveClasses(
@@ -112,19 +126,6 @@
});
}
- private boolean verifyKotlinMetadataModeledForAllClasses(
- Enqueuer enqueuer, boolean keepMetadata) {
- enqueuer.forAllLiveClasses(
- clazz -> {
- // Trace through class and member definitions
- assert !hasKotlinClassMetadataAnnotation(clazz, definitionsForContext(clazz))
- || !keepMetadata
- || !enqueuer.isPinned(clazz.type)
- || clazz.getKotlinInfo() != getNoKotlinInfo();
- });
- return true;
- }
-
public class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier {
private final ProgramDefinition context;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index ccc2ef7..8ac5e71 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -93,7 +93,7 @@
+ Version.LABEL
+ "\n");
if (options.isGeneratingDex()) {
- builder.append("# " + MARKER_KEY_MIN_API + ": " + options.minApiLevel + "\n");
+ builder.append("# " + MARKER_KEY_MIN_API + ": " + options.minApiLevel.getLevel() + "\n");
}
if (Version.isDevelopmentVersion()) {
builder.append(
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index c44e4e7..16b6508 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -4,23 +4,37 @@
package com.android.tools.r8.naming.signature;
+import static com.google.common.base.Predicates.alwaysFalse;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder;
+import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
// TODO(b/169516860): We should generalize this to handle rewriting of attributes in general.
public class GenericSignatureRewriter {
private final AppView<?> appView;
private final NamingLens namingLens;
+ private final GenericSignatureContextBuilder contextBuilder;
public GenericSignatureRewriter(AppView<?> appView, NamingLens namingLens) {
+ this(appView, namingLens, null);
+ }
+
+ public GenericSignatureRewriter(
+ AppView<?> appView, NamingLens namingLens, GenericSignatureContextBuilder contextBuilder) {
this.appView = appView;
this.namingLens = namingLens;
+ this.contextBuilder = contextBuilder;
}
public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
@@ -34,20 +48,54 @@
// arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
// either ProgramClass or has a mapping. This is then transitively called inside the
// ClassNameMinifier.
+ Predicate<DexType> wasPruned =
+ appView.hasLiveness() ? appView.withLiveness().appInfo()::wasPruned : alwaysFalse();
+ BiPredicate<DexType, DexType> hasPrunedRelationship =
+ (enclosing, enclosed) ->
+ contextBuilder.hasPrunedRelationship(appView, enclosing, enclosed, wasPruned);
+ Predicate<DexType> hasGenericTypeVariables =
+ type -> contextBuilder.hasGenericTypeVariables(appView, type, wasPruned);
ThreadUtils.processItems(
classes,
clazz -> {
+ GenericSignaturePartialTypeArgumentApplier classArgumentApplier =
+ contextBuilder != null
+ ? GenericSignaturePartialTypeArgumentApplier.build(
+ appView,
+ contextBuilder.computeTypeParameterContext(
+ appView, clazz.getType(), wasPruned),
+ hasPrunedRelationship,
+ hasGenericTypeVariables)
+ : null;
GenericSignatureTypeRewriter genericSignatureTypeRewriter =
new GenericSignatureTypeRewriter(appView, clazz);
- clazz.setClassSignature(genericSignatureTypeRewriter.rewrite(clazz.getClassSignature()));
+ clazz.setClassSignature(
+ genericSignatureTypeRewriter.rewrite(
+ classArgumentApplier != null
+ ? classArgumentApplier.visitClassSignature(clazz.getClassSignature())
+ : clazz.getClassSignature()));
clazz.forEachField(
field ->
field.setGenericSignature(
- genericSignatureTypeRewriter.rewrite(field.getGenericSignature())));
+ genericSignatureTypeRewriter.rewrite(
+ classArgumentApplier != null
+ ? classArgumentApplier.visitFieldTypeSignature(
+ field.getGenericSignature())
+ : field.getGenericSignature())));
clazz.forEachMethod(
- method ->
- method.setGenericSignature(
- genericSignatureTypeRewriter.rewrite(method.getGenericSignature())));
+ method -> {
+ // The reflection api do not distinguish static methods context and
+ // from virtual methods we therefore always base the context for a method on
+ // the class context.
+ method.setGenericSignature(
+ genericSignatureTypeRewriter.rewrite(
+ classArgumentApplier != null
+ ? classArgumentApplier
+ .buildForMethod(
+ method.getGenericSignature().getFormalTypeParameters())
+ .visitMethodSignature(method.getGenericSignature())
+ : method.getGenericSignature()));
+ });
},
executorService);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
index 3f77dbb..0fdc499 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -41,7 +41,12 @@
}
public static Builder builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
- return new Builder(appView);
+ return builder(appView, appView.graphLens());
+ }
+
+ public static Builder builder(
+ AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens previousLens) {
+ return new Builder(appView, previousLens);
}
@Override
@@ -129,16 +134,55 @@
return getPrevious().isContextFreeForMethods();
}
+ @Override
+ public boolean isMemberRebindingIdentityLens() {
+ return true;
+ }
+
+ @Override
+ public MemberRebindingIdentityLens asMemberRebindingIdentityLens() {
+ return this;
+ }
+
+ public MemberRebindingIdentityLens toRewrittenMemberRebindingIdentityLens(
+ AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ Builder builder = builder(appView, getIdentityLens());
+ nonReboundFieldReferenceToDefinitionMap.forEach(
+ (nonReboundFieldReference, reboundFieldReference) -> {
+ DexField rewrittenReboundFieldReference = lens.lookupField(reboundFieldReference);
+ DexField rewrittenNonReboundFieldReference =
+ rewrittenReboundFieldReference.withHolder(
+ lens.lookupType(nonReboundFieldReference.getHolderType()), dexItemFactory);
+ builder.recordNonReboundFieldAccess(
+ rewrittenNonReboundFieldReference, rewrittenReboundFieldReference);
+ });
+ nonReboundMethodReferenceToDefinitionMap.forEach(
+ (nonReboundMethodReference, reboundMethodReference) -> {
+ DexMethod rewrittenReboundMethodReference =
+ lens.getRenamedMethodSignature(reboundMethodReference);
+ DexMethod rewrittenNonReboundMethodReference =
+ rewrittenReboundMethodReference.withHolder(
+ lens.lookupType(nonReboundMethodReference.getHolderType()), dexItemFactory);
+ builder.recordNonReboundMethodAccess(
+ rewrittenNonReboundMethodReference, rewrittenReboundMethodReference);
+ });
+ return builder.build();
+ }
+
public static class Builder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final GraphLens previousLens;
+
private final Map<DexField, DexField> nonReboundFieldReferenceToDefinitionMap =
new IdentityHashMap<>();
private final Map<DexMethod, DexMethod> nonReboundMethodReferenceToDefinitionMap =
new IdentityHashMap<>();
- private Builder(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ private Builder(AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens previousLens) {
this.appView = appView;
+ this.previousLens = previousLens;
}
void recordNonReboundFieldAccesses(FieldAccessInfo fieldAccessInfo) {
@@ -152,6 +196,12 @@
nonReboundFieldReferenceToDefinitionMap.put(nonReboundFieldReference, reboundFieldReference);
}
+ private void recordNonReboundMethodAccess(
+ DexMethod nonReboundMethodReference, DexMethod reboundMethodReference) {
+ nonReboundMethodReferenceToDefinitionMap.put(
+ nonReboundMethodReference, reboundMethodReference);
+ }
+
void recordMethodAccess(DexMethod reference) {
if (reference.getHolderType().isArrayType()) {
return;
@@ -161,7 +211,7 @@
SingleResolutionResult resolutionResult =
appView.appInfo().resolveMethodOn(holder, reference).asSingleResolution();
if (resolutionResult != null && resolutionResult.getResolvedHolder() != holder) {
- nonReboundMethodReferenceToDefinitionMap.put(
+ recordNonReboundMethodAccess(
reference, resolutionResult.getResolvedMethod().getReference());
}
}
@@ -175,7 +225,7 @@
nonReboundFieldReferenceToDefinitionMap,
nonReboundMethodReferenceToDefinitionMap,
appView.dexItemFactory(),
- appView.graphLens());
+ previousLens);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
index 4310236..d202af8 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.graph.NestedGraphLens.mapVirtualInterfaceInvocationTypes;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
@@ -47,6 +48,11 @@
}
@Override
+ public MemberRebindingLens asMemberRebindingLens() {
+ return this;
+ }
+
+ @Override
public DexType getOriginalType(DexType type) {
return getPrevious().getOriginalType(type);
}
@@ -131,7 +137,8 @@
}
public FieldRebindingIdentityLens toRewrittenFieldRebindingLens(
- DexItemFactory dexItemFactory, GraphLens lens) {
+ AppView<? extends AppInfoWithClassHierarchy> appView, GraphLens lens) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
FieldRebindingIdentityLens.Builder builder = FieldRebindingIdentityLens.builder();
nonReboundFieldReferenceToDefinitionMap.forEach(
(nonReboundFieldReference, reboundFieldReference) -> {
diff --git a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
index 188e749..e193f4c 100644
--- a/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
+++ b/src/main/java/com/android/tools/r8/repackaging/Repackaging.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.graph.NestedGraphLens;
import com.android.tools.r8.graph.ProgramPackage;
import com.android.tools.r8.graph.ProgramPackageCollection;
import com.android.tools.r8.graph.SortedProgramPackageCollection;
@@ -26,7 +27,6 @@
import com.android.tools.r8.shaking.AnnotationFixer;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.synthesis.CommittedItems;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
import com.android.tools.r8.utils.Timing;
@@ -104,25 +104,22 @@
assert false;
}
}.fixupClasses(appView.appInfo().classesWithDeterministicOrder());
- CommittedItems committedItems =
+ NestedGraphLens emptyRepackagingLens =
+ new NestedGraphLens(appView) {
+ @Override
+ protected boolean isLegitimateToHaveEmptyMappings() {
+ return true;
+ }
+ };
+ DirectMappedDexApplication newApplication =
appView
- .getSyntheticItems()
- .commit(
- appView
- .appInfo()
- .app()
- .builder()
- .replaceProgramClasses(new ArrayList<>(newProgramClasses))
- .build());
- if (appView.appInfo().hasLiveness()) {
- appView
- .withLiveness()
- .setAppInfo(appView.withLiveness().appInfo().rebuildWithLiveness(committedItems));
- } else {
- appView
- .withClassHierarchy()
- .setAppInfo(appView.appInfo().rebuildWithClassHierarchy(committedItems));
- }
+ .appInfo()
+ .app()
+ .asDirect()
+ .builder()
+ .replaceProgramClasses(new ArrayList<>(newProgramClasses))
+ .build();
+ appView.rewriteWithLensAndApplication(emptyRepackagingLens, newApplication);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceElement.java b/src/main/java/com/android/tools/r8/retrace/RetraceElement.java
index cb2ac8c..55de558 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceElement.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceElement.java
@@ -3,11 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.retrace;
+import com.android.tools.r8.KeepForSubclassing;
+
/**
* Base interface for any element in a retrace result.
*
* <p>The element represents an unambiguous retracing.
*/
+@KeepForSubclassing
public interface RetraceElement<R extends RetraceResult<?>> {
R getRetraceResultContext();
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index c4ebfe7..239c0da 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -1072,9 +1072,11 @@
objectAllocationInfoCollection.rewrittenWithLens(definitionSupplier, lens),
lens.rewriteCallSites(callSites, definitionSupplier),
keepInfo.rewrite(lens, application.options),
- lens.rewriteReferenceKeys(mayHaveSideEffects),
- lens.rewriteReferenceKeys(noSideEffects),
- lens.rewriteReferenceKeys(assumedValues),
+ // Take any rule in case of collisions.
+ lens.rewriteReferenceKeys(mayHaveSideEffects, ListUtils::first),
+ // Drop assume rules in case of collisions.
+ lens.rewriteReferenceKeys(noSideEffects, rules -> null),
+ lens.rewriteReferenceKeys(assumedValues, rules -> null),
lens.rewriteMethods(alwaysInline),
lens.rewriteMethods(forceInline),
lens.rewriteMethods(neverInline),
diff --git a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
index f8302d7..cb49580 100644
--- a/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/DefaultEnqueuerUseRegistry.java
@@ -13,25 +13,33 @@
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.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.ListIterator;
+import java.util.Map;
public class DefaultEnqueuerUseRegistry extends UseRegistry {
protected final AppView<? extends AppInfoWithClassHierarchy> appView;
private final ProgramMethod context;
protected final Enqueuer enqueuer;
+ private final Map<DexReference, AndroidApiLevel> apiReferenceMapping;
+ private AndroidApiLevel maxApiReferenceLevel;
public DefaultEnqueuerUseRegistry(
AppView<? extends AppInfoWithClassHierarchy> appView,
ProgramMethod context,
- Enqueuer enqueuer) {
+ Enqueuer enqueuer,
+ Map<DexReference, AndroidApiLevel> apiReferenceMapping) {
super(appView.dexItemFactory());
this.appView = appView;
this.context = context;
this.enqueuer = enqueuer;
+ this.apiReferenceMapping = apiReferenceMapping;
+ this.maxApiReferenceLevel = appView.options().minApiLevel;
}
public ProgramMethod getContext() {
@@ -53,26 +61,31 @@
@Override
public void registerInvokeVirtual(DexMethod invokedMethod) {
+ setMaxApiReferenceLevel(invokedMethod);
enqueuer.traceInvokeVirtual(invokedMethod, context);
}
@Override
public void registerInvokeDirect(DexMethod invokedMethod) {
+ setMaxApiReferenceLevel(invokedMethod);
enqueuer.traceInvokeDirect(invokedMethod, context);
}
@Override
public void registerInvokeStatic(DexMethod invokedMethod) {
+ setMaxApiReferenceLevel(invokedMethod);
enqueuer.traceInvokeStatic(invokedMethod, context);
}
@Override
public void registerInvokeInterface(DexMethod invokedMethod) {
+ setMaxApiReferenceLevel(invokedMethod);
enqueuer.traceInvokeInterface(invokedMethod, context);
}
@Override
public void registerInvokeSuper(DexMethod invokedMethod) {
+ setMaxApiReferenceLevel(invokedMethod);
enqueuer.traceInvokeSuper(invokedMethod, context);
}
@@ -158,4 +171,14 @@
super.registerCallSite(callSite);
enqueuer.traceCallSite(callSite, context);
}
+
+ private void setMaxApiReferenceLevel(DexMethod invokedMethod) {
+ this.maxApiReferenceLevel =
+ maxApiReferenceLevel.max(
+ apiReferenceMapping.getOrDefault(invokedMethod, maxApiReferenceLevel));
+ }
+
+ public AndroidApiLevel getMaxApiReferenceLevel() {
+ return maxApiReferenceLevel;
+ }
}
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 f6d9e6e..1023e70 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -102,6 +102,8 @@
import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
import com.android.tools.r8.ir.desugar.LambdaClass;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationInfo;
+import com.android.tools.r8.ir.optimize.info.DefaultMethodOptimizationWithMinApiInfo;
import com.android.tools.r8.kotlin.KotlinMetadataEnqueuerExtension;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.identifiernamestring.IdentifierNameStringLookupResult;
@@ -120,6 +122,7 @@
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
import com.android.tools.r8.synthesis.SyntheticItems.SynthesizingContextOracle;
import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.ListUtils;
@@ -254,6 +257,8 @@
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
+ private final Map<DexReference, AndroidApiLevel> referenceToApiLevelMap;
+
/**
* Tracks the dependency between a method and the super-method it calls, if any. Used to make
* super methods become live when they become reachable from a live sub-method.
@@ -463,6 +468,17 @@
} else {
desugaredLibraryWrapperAnalysis = null;
}
+ referenceToApiLevelMap = new IdentityHashMap<>();
+ if (options.apiModelingOptions().enableApiCallerIdentification) {
+ options
+ .apiModelingOptions()
+ .methodApiMapping
+ .forEach(
+ (methodReference, apiLevel) -> {
+ referenceToApiLevelMap.put(
+ options.dexItemFactory().createMethod(methodReference), apiLevel);
+ });
+ }
}
private AppInfoWithClassHierarchy appInfo() {
@@ -2961,7 +2977,8 @@
RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
this.rootSet = rootSet;
// Translate the result of root-set computation into enqueuer actions.
- if (appView.options().getProguardConfiguration() != null
+ if (mode.isTreeShaking()
+ && appView.options().hasProguardConfiguration()
&& !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
registerAnalysis(
new KotlinMetadataEnqueuerExtension(
@@ -3895,7 +3912,22 @@
}
void traceCode(ProgramMethod method) {
- method.registerCodeReferences(useRegistryFactory.create(appView, method, this));
+ DefaultEnqueuerUseRegistry registry =
+ useRegistryFactory.create(appView, method, this, referenceToApiLevelMap);
+ method.registerCodeReferences(registry);
+ DexEncodedMethod methodDefinition = method.getDefinition();
+ AndroidApiLevel maxApiReferenceLevel = registry.getMaxApiReferenceLevel();
+ assert maxApiReferenceLevel.isGreaterThanOrEqualTo(options.minApiLevel);
+ // To not have mutable update information for all methods that all has min api level we
+ // swap the default optimization info for one with that marks the api level to be min api.
+ if (methodDefinition.getOptimizationInfo() == DefaultMethodOptimizationInfo.getInstance()
+ && maxApiReferenceLevel == options.minApiLevel) {
+ methodDefinition.setMinApiOptimizationInfo(
+ DefaultMethodOptimizationWithMinApiInfo.getInstance());
+ return;
+ }
+ methodDefinition.setOptimizationInfo(
+ methodDefinition.getMutableOptimizationInfo().setApiReferenceLevel(maxApiReferenceLevel));
}
private void checkMemberForSoftPinning(ProgramMember<?, ?> member) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
index c15f8d7..6bd8473 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerUseRegistryFactory.java
@@ -6,13 +6,16 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Map;
public interface EnqueuerUseRegistryFactory {
- UseRegistry create(
+ DefaultEnqueuerUseRegistry create(
AppView<? extends AppInfoWithClassHierarchy> appView,
ProgramMethod currentMethod,
- Enqueuer enqueuer);
+ Enqueuer enqueuer,
+ Map<DexReference, AndroidApiLevel> apiLevelReferenceMap);
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index ef3b4d2..8e78b09 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -44,6 +44,7 @@
private List<DexEncodedMethod> directMethods = new ArrayList<>();
private List<DexEncodedMethod> virtualMethods = new ArrayList<>();
private List<SyntheticMethodBuilder> methods = new ArrayList<>();
+ private ClassSignature signature = ClassSignature.noSignature();
SyntheticClassBuilder(DexType type, SynthesizingContext context, DexItemFactory factory) {
this.factory = factory;
@@ -87,6 +88,11 @@
return self();
}
+ public B setGenericSignature(ClassSignature signature) {
+ this.signature = signature;
+ return self();
+ }
+
public B setStaticFields(List<DexEncodedField> fields) {
staticFields.clear();
staticFields.addAll(fields);
@@ -153,7 +159,7 @@
nestMembers,
enclosingMembers,
innerClasses,
- ClassSignature.noSignature(),
+ signature,
DexAnnotationSet.empty(),
staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
instanceFields.toArray(DexEncodedField.EMPTY_ARRAY),
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index ba68e81..7c1decc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -71,6 +71,7 @@
ClassToFeatureSplitMap classToFeatureSplitMap,
SyntheticItems syntheticItems) {
Hasher hasher = Hashing.murmur3_128().newHasher();
+ hasher.putInt(kind.id);
if (getKind().isFixedSuffixSynthetic) {
// Fixed synthetics are non-shareable. Its unique type is used as the hash key.
getHolder().getType().hash(hasher);
@@ -101,6 +102,12 @@
boolean includeContext,
GraphLens graphLens,
ClassToFeatureSplitMap classToFeatureSplitMap) {
+ {
+ int order = kind.compareTo(other.getKind());
+ if (order != 0) {
+ return order;
+ }
+ }
DexType thisType = getHolder().getType();
DexType otherType = other.getHolder().getType();
if (getKind().isFixedSuffixSynthetic) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index 55b5e00..a07b61b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -48,6 +48,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
@@ -549,9 +550,9 @@
(externalSyntheticTypePrefix, groups) -> {
// Sort the equivalence groups that go into 'context' including the context type of the
// representative which is equal to 'context' here (see assert below).
- groups.sort(
- (a, b) ->
- a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap));
+ Comparator<EquivalenceGroup<T>> comparator =
+ (a, b) -> a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap);
+ ListUtils.destructiveSort(groups, comparator);
for (int i = 0; i < groups.size(); i++) {
EquivalenceGroup<T> group = groups.get(i);
assert group
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index b5fc30d..ffaad01 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -22,7 +22,8 @@
public class SyntheticMarker {
- private static final String SYNTHETIC_MARKER_ATTRIBUTE_TYPE_NAME = "R8SynthesizedClass";
+ private static final String SYNTHETIC_MARKER_ATTRIBUTE_TYPE_NAME =
+ "com.android.tools.r8.SynthesizedClass";
public static Attribute getMarkerAttributePrototype() {
return MarkerAttribute.PROTOTYPE;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index ce1647b..20f0ab4 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -207,6 +207,11 @@
createDescriptor(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context.getBinaryName(), id));
}
+ public static boolean isInternalStaticInterfaceCall(ClassReference reference) {
+ return SyntheticNaming.isSynthetic(
+ reference, Phase.INTERNAL, SyntheticKind.STATIC_INTERFACE_CALL);
+ }
+
static boolean isSynthetic(ClassReference clazz, Phase phase, SyntheticKind kind) {
String typeName = clazz.getTypeName();
if (kind.isFixedSuffixSynthetic) {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 523552a..6ea2f40 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -40,7 +40,8 @@
P(28),
Q(29),
R(30),
- S(31);
+ S(31),
+ UNKNOWN(10000);
public static final AndroidApiLevel LATEST = S;
@@ -64,6 +65,10 @@
return AndroidApiLevel.B;
}
+ public AndroidApiLevel max(AndroidApiLevel other) {
+ return Ordered.max(this, other);
+ }
+
public DexVersion getDexVersion() {
return DexVersion.getDexVersion(this);
}
@@ -88,10 +93,9 @@
}
public static AndroidApiLevel getAndroidApiLevel(int apiLevel) {
+ assert apiLevel > 0;
+ assert UNKNOWN.isGreaterThan(LATEST);
switch (apiLevel) {
- case 0:
- // 0 is not supported, it should not happen
- throw new Unreachable();
case 1:
return B;
case 2:
@@ -152,8 +156,12 @@
return Q;
case 30:
return R;
+ case 31:
+ return S;
default:
- return LATEST;
+ // This has to be updated when we add new api levels.
+ assert S == LATEST;
+ return UNKNOWN;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/CfVersionUtils.java b/src/main/java/com/android/tools/r8/utils/CfVersionUtils.java
index dadf460..01ec7c1 100644
--- a/src/main/java/com/android/tools/r8/utils/CfVersionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/CfVersionUtils.java
@@ -5,17 +5,17 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.cf.CfVersion;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.utils.structural.Ordered;
import java.util.List;
public class CfVersionUtils {
- public static CfVersion max(List<DexEncodedMethod> methods) {
+ public static CfVersion max(List<ProgramMethod> methods) {
CfVersion result = null;
- for (DexEncodedMethod method : methods) {
- if (method.hasClassFileVersion()) {
- result = Ordered.maxIgnoreNull(result, method.getClassFileVersion());
+ for (ProgramMethod method : methods) {
+ if (method.getDefinition().hasClassFileVersion()) {
+ result = Ordered.maxIgnoreNull(result, method.getDefinition().getClassFileVersion());
}
}
return result;
diff --git a/src/main/java/com/android/tools/r8/utils/DexVersion.java b/src/main/java/com/android/tools/r8/utils/DexVersion.java
index f06f3d7..65ac80a 100644
--- a/src/main/java/com/android/tools/r8/utils/DexVersion.java
+++ b/src/main/java/com/android/tools/r8/utils/DexVersion.java
@@ -38,6 +38,9 @@
public static DexVersion getDexVersion(AndroidApiLevel androidApiLevel) {
switch (androidApiLevel) {
+ // UNKNOWN is an unknown higher api version we therefore choose the highest known
+ // version.
+ case UNKNOWN:
case S:
case R:
case Q:
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 12126a4..9003eca 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -85,7 +85,6 @@
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -349,9 +348,6 @@
// Contain the contents of the build properties file from the compiler command.
public DumpOptions dumpOptions;
- // A mapping from methods to the api-level introducing them.
- public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
-
// Hidden marker for classes.dex
private boolean hasMarker = false;
private Marker marker;
@@ -383,7 +379,7 @@
// since the output depends on the min API in this case. There is basically no min API entry
// in R8 cf to cf.
if (isGeneratingDex() || desugarState == DesugarState.ON) {
- marker.setMinApi(minApiLevel);
+ marker.setMinApi(minApiLevel.getLevel());
}
if (desugaredLibraryConfiguration.getIdentifier() != null) {
marker.setDesugaredLibraryIdentifiers(desugaredLibraryConfiguration.getIdentifier());
@@ -526,7 +522,7 @@
getExtensiveInterfaceMethodMinifierLoggingFilter();
public List<String> methodsFilter = ImmutableList.of();
- public int minApiLevel = AndroidApiLevel.getDefault().getLevel();
+ public AndroidApiLevel minApiLevel = AndroidApiLevel.getDefault();
// Skipping min_api check and compiling an intermediate result intended for later merging.
// Intermediate builds also emits or update synthesized classes mapping.
public boolean intermediate = false;
@@ -657,6 +653,7 @@
public boolean ignoreMissingClasses = false;
public boolean reportMissingClassesInEnclosingMethodAttribute = false;
public boolean reportMissingClassesInInnerClassAttributes = false;
+ public boolean disableGenericSignatureValidation = false;
// EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
public boolean forceProguardCompatibility = false;
@@ -675,6 +672,7 @@
private final ProtoShrinkingOptions protoShrinking = new ProtoShrinkingOptions();
private final KotlinOptimizationOptions kotlinOptimizationOptions =
new KotlinOptimizationOptions();
+ private final ApiModelTestingOptions apiModelTestingOptions = new ApiModelTestingOptions();
private final DesugarSpecificOptions desugarSpecificOptions = new DesugarSpecificOptions();
public final TestingOptions testing = new TestingOptions();
@@ -707,6 +705,10 @@
return kotlinOptimizationOptions;
}
+ public ApiModelTestingOptions apiModelingOptions() {
+ return apiModelTestingOptions;
+ }
+
public DesugarSpecificOptions desugarSpecificOptions() {
return desugarSpecificOptions;
}
@@ -1215,7 +1217,8 @@
private boolean enable =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
- private boolean enableInterfaceMerging = false;
+ private boolean enableInterfaceMergingInInitial = false;
+ private boolean enableInterfaceMergingInFinal = false;
private boolean enableSyntheticMerging = true;
private boolean ignoreRuntimeTypeChecksForTesting = false;
private boolean restrictToSynthetics = false;
@@ -1238,10 +1241,6 @@
this.enable = enable;
}
- public void enableInterfaceMerging() {
- enableInterfaceMerging = true;
- }
-
public int getMaxGroupSize() {
return maxGroupSize;
}
@@ -1265,23 +1264,30 @@
return ignoreRuntimeTypeChecksForTesting;
}
- public boolean isInterfaceMergingEnabled() {
- assert !isInterfaceMergingEnabled(HorizontalClassMerger.Mode.INITIAL);
- return isInterfaceMergingEnabled(HorizontalClassMerger.Mode.FINAL);
- }
-
public boolean isSyntheticMergingEnabled() {
return enableSyntheticMerging;
}
public boolean isInterfaceMergingEnabled(HorizontalClassMerger.Mode mode) {
- return enableInterfaceMerging && mode.isFinal();
+ if (mode.isInitial()) {
+ return enableInterfaceMergingInInitial;
+ }
+ assert mode.isFinal();
+ return enableInterfaceMergingInFinal;
}
public boolean isRestrictedToSynthetics() {
return restrictToSynthetics || !isOptimizing() || !isShrinking();
}
+ public void setEnableInterfaceMergingInInitial() {
+ enableInterfaceMergingInInitial = true;
+ }
+
+ public void setEnableInterfaceMergingInFinal() {
+ enableInterfaceMergingInFinal = true;
+ }
+
public void setIgnoreRuntimeTypeChecksForTesting() {
ignoreRuntimeTypeChecksForTesting = true;
}
@@ -1291,6 +1297,14 @@
}
}
+ public static class ApiModelTestingOptions {
+
+ // A mapping from methods to the api-level introducing them.
+ public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
+
+ public boolean enableApiCallerIdentification = false;
+ }
+
public static class ProtoShrinkingOptions {
public boolean enableGeneratedExtensionRegistryShrinking = false;
@@ -1351,8 +1365,8 @@
public BiConsumer<DexItemFactory, HorizontallyMergedClasses> horizontallyMergedClassesConsumer =
ConsumerUtils.emptyBiConsumer();
- public BiFunction<Iterable<DexProgramClass>, DexProgramClass, DexProgramClass>
- horizontalClassMergingTarget = (candidates, target) -> target;
+ public TriFunction<AppView<?>, Iterable<DexProgramClass>, DexProgramClass, DexProgramClass>
+ horizontalClassMergingTarget = (appView, candidates, target) -> target;
public BiConsumer<DexItemFactory, EnumDataMap> unboxedEnumsConsumer =
ConsumerUtils.emptyBiConsumer();
@@ -1505,7 +1519,7 @@
}
private boolean hasMinApi(AndroidApiLevel level) {
- return minApiLevel >= level.getLevel();
+ return minApiLevel.isGreaterThanOrEqualTo(level);
}
/**
@@ -1588,7 +1602,7 @@
// the highest known API level when the compiler is built. This ensures that when this is used
// by the Android Platform build (which normally use an API level of 10000) there will be
// no rewriting of backported methods. See b/147480264.
- return desugarState.isOn() && minApiLevel <= AndroidApiLevel.LATEST.getLevel();
+ return desugarState.isOn() && minApiLevel.isLessThanOrEqualTo(AndroidApiLevel.LATEST);
}
public boolean enableTryWithResourcesDesugaring() {
@@ -1690,7 +1704,7 @@
// being thrown on out of bounds.
public boolean canUseSameArrayAndResultRegisterInArrayGetWide() {
assert isGeneratingDex();
- return minApiLevel > AndroidApiLevel.O_MR1.getLevel();
+ return minApiLevel.isGreaterThan(AndroidApiLevel.O_MR1);
}
// Some Lollipop versions of Art found in the wild perform invalid bounds
@@ -1707,7 +1721,7 @@
//
// See b/69364976 and b/77996377.
public boolean canHaveBoundsCheckEliminationBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.M.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.M);
}
// MediaTek JIT compilers for KitKat phones did not implement the not
@@ -1723,7 +1737,7 @@
// assumed to not change. If the receiver register is reused for something else the verifier
// will fail and the code will not run.
public boolean canHaveThisTypeVerifierBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.M.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.M);
}
// Art crashes if we do dead reference elimination of the receiver in release mode and Art
@@ -1732,13 +1746,13 @@
//
// See b/116683601 and b/116837585.
public boolean canHaveThisJitCodeDebuggingBug() {
- return minApiLevel < AndroidApiLevel.Q.getLevel();
+ return minApiLevel.isLessThan(AndroidApiLevel.Q);
}
// The dalvik jit had a bug where the long operations add, sub, or, xor and and would write
// the first part of the result long before reading the second part of the input longs.
public boolean canHaveOverlappingLongRegisterBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.L.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.L);
}
// Some dalvik versions found in the wild perform invalid JIT compilation of cmp-long
@@ -1771,7 +1785,7 @@
//
// See b/75408029.
public boolean canHaveCmpLongBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.L.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.L);
}
// Some Lollipop VMs crash if there is a const instruction between a cmp and an if instruction.
@@ -1799,7 +1813,7 @@
//
// See b/115552239.
public boolean canHaveCmpIfFloatBug() {
- return minApiLevel < AndroidApiLevel.M.getLevel();
+ return minApiLevel.isLessThan(AndroidApiLevel.M);
}
// Some Lollipop VMs incorrectly optimize code with mul2addr instructions. In particular,
@@ -1821,7 +1835,7 @@
//
// This issue has only been observed on a Verizon Ellipsis 8 tablet. See b/76115465.
public boolean canHaveMul2AddrBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.M.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.M);
}
// Some Marshmallow VMs create an incorrect doubly-linked list of instructions. When the VM
@@ -1830,7 +1844,7 @@
//
// See b/77842465.
public boolean canHaveDex2OatLinkedListBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.N.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.N);
}
// dex2oat on Marshmallow VMs does aggressive inlining which can eat up all the memory on
@@ -1838,7 +1852,7 @@
//
// See b/111960171
public boolean canHaveDex2OatInliningIssue() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.N.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.N);
}
// Art 7.0.0 and later Art JIT may perform an invalid optimization if a string new-instance does
@@ -1846,7 +1860,7 @@
//
// See b/78493232 and b/80118070.
public boolean canHaveArtStringNewInitBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.Q.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.Q);
}
// Dalvik tracing JIT may perform invalid optimizations when int/float values are converted to
@@ -1854,7 +1868,7 @@
//
// See b/77496850.
public boolean canHaveNumberConversionRegisterAllocationBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.L.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.L);
}
// Some Lollipop mediatek VMs have a peculiar bug where the inliner crashes if there is a
@@ -1867,7 +1881,7 @@
//
// See b/68378480.
public boolean canHaveForwardingInitInliningBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.M.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.M);
}
// Some Lollipop x86_64 VMs have a bug causing a segfault if an exception handler directly targets
@@ -1879,7 +1893,7 @@
//
// See b/111337896.
public boolean canHaveExceptionTargetingLoopHeaderBug() {
- return isGeneratingDex() && !debug && minApiLevel < AndroidApiLevel.M.getLevel();
+ return isGeneratingDex() && !debug && minApiLevel.isLessThan(AndroidApiLevel.M);
}
// The Dalvik tracing JIT can trace past the end of the instruction stream and end up
@@ -1894,7 +1908,7 @@
// We also could not insert any dead code (e.g. a return) because that would make mediatek
// dominator calculations on 7.0.0 crash. See b/128926846.
public boolean canHaveTracingPastInstructionsStreamBug() {
- return minApiLevel < AndroidApiLevel.L.getLevel();
+ return minApiLevel.isLessThan(AndroidApiLevel.L);
}
// The art verifier incorrectly propagates type information for the following pattern:
@@ -1922,7 +1936,7 @@
// Fixed in Android Q, see b/120985556.
public boolean canHaveArtInstanceOfVerifierBug() {
assert isGeneratingDex();
- return minApiLevel < AndroidApiLevel.Q.getLevel();
+ return minApiLevel.isLessThan(AndroidApiLevel.Q);
}
// Some Art Lollipop version do not deal correctly with long-to-int conversions.
@@ -1945,7 +1959,7 @@
public boolean canHaveLongToIntBug() {
// We have only seen this happening on Lollipop arm64 backends. We have tested on
// Marshmallow and Nougat arm64 devices and they do not have the bug.
- return minApiLevel < AndroidApiLevel.M.getLevel();
+ return minApiLevel.isLessThan(AndroidApiLevel.M);
}
// The Art VM for Android N through P has a bug in the JIT that means that if the same
@@ -1958,7 +1972,7 @@
//
// See b/120164595.
public boolean canHaveExceptionTypeBug() {
- return minApiLevel < AndroidApiLevel.Q.getLevel();
+ return minApiLevel.isLessThan(AndroidApiLevel.Q);
}
// Art 4.0.4 fails with a verification error when a null-literal is being passed directly to an
@@ -1966,7 +1980,7 @@
// elimination of check-cast instructions where the value being cast is the constant null.
// See b/123269162.
public boolean canHaveArtCheckCastVerifierBug() {
- return minApiLevel < AndroidApiLevel.J.getLevel();
+ return minApiLevel.isLessThan(AndroidApiLevel.J);
}
// The verifier will merge A[] and B[] to Object[], even when both A and B implement an interface
@@ -1990,7 +2004,7 @@
//
// See b/131349148
public boolean canHaveDalvikCatchHandlerVerificationBug() {
- return isGeneratingClassFiles() || minApiLevel < AndroidApiLevel.L.getLevel();
+ return isGeneratingClassFiles() || minApiLevel.isLessThan(AndroidApiLevel.L);
}
// Having an invoke instruction that targets an abstract method on a non-abstract class will fail
@@ -1998,7 +2012,7 @@
//
// See b/132953944.
public boolean canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.L.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.L);
}
// On dalvik we see issues when using an int value in places where a boolean, byte, char, or short
@@ -2012,14 +2026,14 @@
//
// See also b/134304597 and b/124152497.
public boolean canHaveDalvikIntUsedAsNonIntPrimitiveTypeBug() {
- return isGeneratingClassFiles() || minApiLevel < AndroidApiLevel.L.getLevel();
+ return isGeneratingClassFiles() || minApiLevel.isLessThan(AndroidApiLevel.L);
}
// The standard library prior to API 19 did not contain a ZipFile that implemented Closable.
//
// See b/177532008.
public boolean canHaveZipFileWithMissingCloseableBug() {
- return isGeneratingClassFiles() || minApiLevel < AndroidApiLevel.K.getLevel();
+ return isGeneratingClassFiles() || minApiLevel.isLessThan(AndroidApiLevel.K);
}
// Some versions of Dalvik had a bug where a switch with a MAX_INT key would still go to
@@ -2027,7 +2041,7 @@
//
// See b/177790310.
public boolean canHaveSwitchMaxIntBug() {
- return isGeneratingDex() && minApiLevel < AndroidApiLevel.K.getLevel();
+ return isGeneratingDex() && minApiLevel.isLessThan(AndroidApiLevel.K);
}
// On Dalvik the methods Integer.parseInt and Long.parseLong does not support strings with a '+'
@@ -2035,6 +2049,6 @@
//
// See b/182137865.
public boolean canParseNumbersWithPlusPrefix() {
- return minApiLevel > AndroidApiLevel.K.getLevel();
+ return minApiLevel.isGreaterThan(AndroidApiLevel.K);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 1c260e3..5781f91 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -7,6 +7,7 @@
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
@@ -147,6 +148,13 @@
return list;
}
+ public static <T> ArrayList<T> newArrayList(T element, T other) {
+ ArrayList<T> list = new ArrayList<>();
+ list.add(element);
+ list.add(other);
+ return list;
+ }
+
public static <T> ArrayList<T> newArrayList(ForEachable<T> forEachable) {
ArrayList<T> list = new ArrayList<>();
forEachable.forEach(list::add);
@@ -209,4 +217,36 @@
public interface ReferenceAndIntConsumer<T> {
void accept(T item, int index);
}
+
+ public static <T> void destructiveSort(List<T> items, Comparator<T> comparator) {
+ items.sort(comparator);
+ }
+
+ // Utility to add a slow verification of a comparator as part of sorting. Note that this
+ // should not generally be used in asserts unless the quadratic behavior can be tolerated.
+ public static <T> void destructiveSortAndVerify(List<T> items, Comparator<T> comparator) {
+ destructiveSort(items, comparator);
+ assert verifyComparatorOnSortedList(items, comparator);
+ }
+
+ private static <T> boolean verifyComparatorOnSortedList(List<T> items, Comparator<T> comparator) {
+ for (int i = 0; i < items.size(); i++) {
+ boolean allowEqual = true;
+ for (int j = i; j < items.size(); j++) {
+ T a = items.get(i);
+ T b = items.get(j);
+ int result1 = comparator.compare(a, b);
+ int result2 = comparator.compare(b, a);
+ boolean isEqual = result1 == 0 && result2 == 0;
+ if (i == j) {
+ assert isEqual;
+ } else if (!allowEqual || !isEqual) {
+ allowEqual = false;
+ assert result1 < 0;
+ assert result2 > 0;
+ }
+ }
+ }
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index 63e881e..fc39146 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -50,6 +51,12 @@
return result;
}
+ public static <K, V> IdentityHashMap<K, V> newIdentityHashMap(BiForEachable<K, V> forEachable) {
+ IdentityHashMap<K, V> map = new IdentityHashMap<>();
+ forEachable.forEach(map::put);
+ return map;
+ }
+
public static <T> void removeIdentityMappings(Map<T, T> map) {
map.entrySet().removeIf(entry -> entry.getKey() == entry.getValue());
}
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 4279cb7..67e4148 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -48,6 +48,10 @@
return workList;
}
+ public static <T> WorkList<T> newWorkList(Set<T> seen) {
+ return new WorkList<>(seen);
+ }
+
private WorkList(EqualityTest equalityTest) {
this(equalityTest == EqualityTest.HASH ? new HashSet<>() : Sets.newIdentityHashSet());
}
@@ -56,6 +60,10 @@
this.seen = seen;
}
+ public void addIgnoringSeenSet(T item) {
+ workingList.addLast(item);
+ }
+
public void addAllIgnoringSeenSet(Iterable<T> items) {
items.forEach(workingList::addLast);
}
@@ -86,6 +94,10 @@
return !hasNext();
}
+ public boolean isSeen(T item) {
+ return seen.contains(item);
+ }
+
public void markAsSeen(T item) {
seen.add(item);
}
@@ -103,6 +115,10 @@
return Collections.unmodifiableSet(seen);
}
+ public Set<T> getMutableSeenSet() {
+ return seen;
+ }
+
public enum EqualityTest {
HASH,
IDENTITY
diff --git a/src/test/examples/classmerging/NoHorizontalClassMerging.java b/src/test/examples/classmerging/NoHorizontalClassMerging.java
new file mode 100644
index 0000000..3b7a831
--- /dev/null
+++ b/src/test/examples/classmerging/NoHorizontalClassMerging.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package classmerging;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface NoHorizontalClassMerging {}
diff --git a/src/test/examples/classmerging/SimpleInterfaceAccessTest.java b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
index 6fde90e..804b4ed 100644
--- a/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
+++ b/src/test/examples/classmerging/SimpleInterfaceAccessTest.java
@@ -32,12 +32,14 @@
}
// Should only be merged into OtherSimpleInterfaceImpl if access modifications are allowed.
+ @NoHorizontalClassMerging
public interface SimpleInterface {
void foo();
}
// Should only be merged into OtherSimpleInterfaceImpl if access modifications are allowed.
+ @NoHorizontalClassMerging
public interface OtherSimpleInterface {
void bar();
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 64ce874..46fcb91 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -72,3 +72,4 @@
-neverinline class * {
@classmerging.NeverInline <methods>;
}
+-nohorizontalclassmerging @classmerging.NoHorizontalClassMerging class *
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index a8b8a49..a3d5cf6 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -29,7 +29,7 @@
protected void ensureSameOutput(String main, AndroidApiLevel apiLevel,
List<String> args, byte[]... classes) throws Exception {
AndroidApp app = buildAndroidApp(classes);
- Consumer<InternalOptions> setMinApiLevel = o -> o.minApiLevel = apiLevel.getLevel();
+ Consumer<InternalOptions> setMinApiLevel = o -> o.minApiLevel = apiLevel;
ProcessResult javaResult = runOnJavaRaw(main, Arrays.asList(classes), args);
Consumer<ArtCommandBuilder> cmdBuilder = builder -> {
for (String arg : args) {
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
index a800801..6517f6e 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -100,6 +100,16 @@
explain(description.appendText("a diagnostic "));
}
+ @Override
+ protected void describeMismatchSafely(Diagnostic item, Description mismatchDescription) {
+ mismatchDescription
+ .appendText("was ")
+ .appendText(item.getClass().getName())
+ .appendText(" with message(")
+ .appendValue(item.getDiagnosticMessage())
+ .appendText(")");
+ }
+
protected abstract boolean eval(Diagnostic diagnostic);
protected abstract void explain(Description description);
diff --git a/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java b/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
index 568aa74..8074250 100644
--- a/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8CompatTestBuilder.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
-import java.nio.file.Path;
public class R8CompatTestBuilder extends R8TestBuilder<R8CompatTestBuilder> {
@@ -21,6 +20,11 @@
}
@Override
+ public boolean isR8CompatTestBuilder() {
+ return true;
+ }
+
+ @Override
R8CompatTestBuilder self() {
return this;
}
diff --git a/src/test/java/com/android/tools/r8/R8FullTestBuilder.java b/src/test/java/com/android/tools/r8/R8FullTestBuilder.java
index 3343329..a8f6d60 100644
--- a/src/test/java/com/android/tools/r8/R8FullTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8FullTestBuilder.java
@@ -24,6 +24,11 @@
}
@Override
+ public boolean isR8TestBuilder() {
+ return true;
+ }
+
+ @Override
R8FullTestBuilder self() {
return this;
}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 95198ec..bcd4fea 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -191,6 +191,9 @@
// TODO(zerny): Amend flaky tests with an expected flaky result to track issues.
private static Multimap<String, TestCondition> flakyRunWithArt =
new ImmutableListMultimap.Builder<String, TestCondition>()
+ // Sometime output also appends "Actually slept about X msec..."
+ // So far only seen on the 5.1.1 runtime.
+ .put("002-sleep", TestCondition.match(TestCondition.runtimes(DexVm.Version.V5_1_1)))
// Can crash but mostly passes
// Crashes:
// check_reference_map_visitor.h:44] At Main.f
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 488480c..608f635 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -108,7 +108,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 16, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -147,7 +147,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 18, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 16, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index d78af9d..0e11c5b 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -5,7 +5,7 @@
import static com.android.tools.r8.dexsplitter.SplitterTestBase.simpleSplitProvider;
import static com.android.tools.r8.dexsplitter.SplitterTestBase.splitWithNonJavaFile;
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.proguardConfigurationRuleDoesNotMatch;
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
@@ -139,8 +139,7 @@
case NONE:
if (allowUnusedProguardConfigurationRules) {
compileResult
- .assertAllInfoMessagesMatch(
- containsString("Proguard configuration rule does not match anything"))
+ .assertAllInfosMatch(proguardConfigurationRuleDoesNotMatch())
.assertNoErrorMessages()
.assertNoWarningMessages();
} else {
@@ -154,11 +153,9 @@
throw new Unreachable();
}
if (allowUnusedProguardConfigurationRules) {
- compileResult.assertInfoMessageThatMatches(
- containsString("Proguard configuration rule does not match anything"));
+ compileResult.assertInfoThatMatches(proguardConfigurationRuleDoesNotMatch());
} else {
- compileResult.assertNoInfoMessageThatMatches(
- containsString("Proguard configuration rule does not match anything"));
+ compileResult.assertNoInfoThatMatches(proguardConfigurationRuleDoesNotMatch());
}
return compileResult;
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index b24d16a..19c77fb 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.TestBuilder.getTestingAnnotations;
import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import static com.google.common.collect.Lists.cartesianProduct;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -1827,4 +1828,17 @@
extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return extractor.getClassFileVersion();
}
+
+ public static void verifyAllInfoFromGenericSignatureTypeParameterValidation(
+ TestCompileResult<?, ?> compileResult) {
+ compileResult.assertAtLeastOneInfoMessage();
+ compileResult.assertAllInfoMessagesMatch(containsString("A type variable is not in scope"));
+ }
+
+ public static void verifyExpectedInfoFromGenericSignatureSuperTypeValidation(
+ TestCompileResult<?, ?> compileResult) {
+ compileResult.assertAtLeastOneInfoMessage();
+ compileResult.assertAllInfoMessagesMatch(
+ containsString("The generic super type is not the same as the class super type"));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 5788681..827684d 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -358,15 +358,30 @@
return self();
}
+ public CR assertInfoThatMatches(Matcher<Diagnostic> matcher) {
+ getDiagnosticMessages().assertInfoThatMatches(matcher);
+ return self();
+ }
+
public CR assertInfoMessageThatMatches(Matcher<String> matcher) {
getDiagnosticMessages().assertInfoThatMatches(diagnosticMessage(matcher));
return self();
}
+ public CR assertAllInfosMatch(Matcher<Diagnostic> matcher) {
+ getDiagnosticMessages().assertNoInfosMatch(not(matcher));
+ return self();
+ }
+
public CR assertAllInfoMessagesMatch(Matcher<String> matcher) {
return assertNoInfoMessageThatMatches(not(matcher));
}
+ public CR assertNoInfoThatMatches(Matcher<Diagnostic> matcher) {
+ getDiagnosticMessages().assertNoInfosMatch(matcher);
+ return self();
+ }
+
public CR assertNoInfoMessageThatMatches(Matcher<String> matcher) {
getDiagnosticMessages().assertNoInfosMatch(diagnosticMessage(matcher));
return self();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index aad62cb..c421c83 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -44,6 +44,14 @@
return false;
}
+ public boolean isR8TestBuilder() {
+ return false;
+ }
+
+ public boolean isR8CompatTestBuilder() {
+ return false;
+ }
+
@Override
public boolean isTestShrinkerBuilder() {
return true;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 5a9dba5..4c765be 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -826,6 +826,7 @@
case J_MR1:
case J_MR2:
case K_WATCH:
+ case UNKNOWN:
return false;
default:
return true;
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
new file mode 100644
index 0000000..4e5c39d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiModelNoInliningOfHigherApiLevelInterfaceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public ApiModelNoInliningOfHigherApiLevelInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("callInterfaceMethod", Api.class);
+ Method apiCallerCaller = A.class.getDeclaredMethod("noApiCall");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class, ApiCaller.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A::noApiCall", "ApiCaller::callInterfaceMethod")
+ .inspect(
+ verifyThat(parameters, apiCaller)
+ .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1));
+ }
+
+ public interface Api {
+
+ void apiLevel22();
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ public static void callInterfaceMethod(Api api) {
+ System.out.println("ApiCaller::callInterfaceMethod");
+ if (api != null) {
+ api.apiLevel22();
+ }
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class A {
+
+ @NeverInline
+ public static void noApiCall() {
+ System.out.println("A::noApiCall");
+ ApiCaller.callInterfaceMethod(null);
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.noApiCall();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
new file mode 100644
index 0000000..048874d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoInliningOfHigherApiLevelIntoLowerDirectTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test()
+ public void testR8() throws Exception {
+ Method apiLevel21 = A.class.getDeclaredMethod("apiLevel21");
+ Method apiLevel22 = B.class.getDeclaredMethod("apiLevel22");
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForMethod(apiLevel21, AndroidApiLevel.L))
+ .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A::apiLevel21", "B::apiLevel22")
+ .inspect(verifyThat(parameters, apiLevel22).inlinedInto(apiLevel21));
+ }
+
+ // This tests that program classes where we directly mock the methods to have an api level will
+ // be inlined.
+ @NoHorizontalClassMerging
+ public static class B {
+ public static void apiLevel22() {
+ System.out.println("B::apiLevel22");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class A {
+
+ @NeverInline
+ public static void apiLevel21() {
+ System.out.println("A::apiLevel21");
+ B.apiLevel22();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.apiLevel21();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java
new file mode 100644
index 0000000..dfb8628
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticTest.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiModelNoInliningOfHigherApiLevelStaticTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoInliningOfHigherApiLevelStaticTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("callStaticMethod");
+ Method apiCallerCaller = A.class.getDeclaredMethod("noApiCall");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class, ApiCaller.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .inspect(
+ verifyThat(parameters, apiCaller)
+ .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "A::noApiCall", "ApiCaller::callStaticMethod", "Api::apiLevel22");
+ }
+
+ public static class Api {
+
+ public static void apiLevel22() {
+ System.out.println("Api::apiLevel22");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+ public static void callStaticMethod() {
+ System.out.println("ApiCaller::callStaticMethod");
+ Api.apiLevel22();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class A {
+
+ @NeverInline
+ public static void noApiCall() {
+ System.out.println("A::noApiCall");
+ ApiCaller.callStaticMethod();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.noApiCall();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java
new file mode 100644
index 0000000..415a163
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelSuperTest.java
@@ -0,0 +1,97 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiModelNoInliningOfHigherApiLevelSuperTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public ApiModelNoInliningOfHigherApiLevelSuperTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("apiLevel22");
+ Method apiCallerCaller = A.class.getDeclaredMethod("noApiCall");
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .inspect(
+ verifyThat(parameters, apiCaller)
+ .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A::noApiCall", "ApiCaller::apiLevel22", "Api::apiLevel22");
+ }
+
+ @NoVerticalClassMerging
+ public static class Api {
+
+ void apiLevel22() {
+ System.out.println("Api::apiLevel22");
+ }
+ }
+
+ @NeverClassInline
+ public static class ApiCaller extends Api {
+
+ @Override
+ void apiLevel22() {
+ System.out.println("ApiCaller::apiLevel22");
+ super.apiLevel22();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class A {
+
+ @NeverInline
+ public static void noApiCall() {
+ System.out.println("A::noApiCall");
+ new ApiCaller().apiLevel22();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.noApiCall();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
new file mode 100644
index 0000000..31af9e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelVirtualTest.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelNoInliningOfHigherApiLevelVirtualTest.ApiCaller.callVirtualMethod;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+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 ApiModelNoInliningOfHigherApiLevelVirtualTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public ApiModelNoInliningOfHigherApiLevelVirtualTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("callVirtualMethod");
+ Method apiCallerCaller = A.class.getDeclaredMethod("noApiCall");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, A.class, ApiCaller.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .inspect(
+ verifyThat(parameters, apiCaller)
+ .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "A::noApiCall", "ApiCaller::callVirtualMethod", "Api::apiLevel22");
+ }
+
+ public static class Api {
+
+ public void apiLevel22() {
+ System.out.println("Api::apiLevel22");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ public static void callVirtualMethod() {
+ System.out.println("ApiCaller::callVirtualMethod");
+ new Api().apiLevel22();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class A {
+
+ @NeverInline
+ public static void noApiCall() {
+ System.out.println("A::noApiCall");
+ callVirtualMethod();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.noApiCall();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningTestHelper.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
similarity index 69%
rename from src/test/java/com/android/tools/r8/apioutlining/ApiOutliningTestHelper.java
rename to src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
index f10a6e9..5b1a2f7 100644
--- a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
@@ -2,7 +2,7 @@
// 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.apioutlining;
+package com.android.tools.r8.apimodeling;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.not;
@@ -19,36 +19,45 @@
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.lang.reflect.Method;
-public abstract class ApiOutliningTestHelper {
+public abstract class ApiModelingTestHelper {
static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
ThrowableConsumer<T> setMockApiLevelForMethod(Method method, AndroidApiLevel apiLevel) {
return compilerBuilder -> {
compilerBuilder.addOptionsModification(
options -> {
- options.methodApiMapping.put(Reference.methodFromMethod(method), apiLevel);
+ options
+ .apiModelingOptions()
+ .methodApiMapping
+ .put(Reference.methodFromMethod(method), apiLevel);
});
};
}
- static ApiOutliningMethodVerificationHelper verifyThat(TestParameters parameters, Method method) {
- return new ApiOutliningMethodVerificationHelper(parameters, method);
+ static void enableApiCallerIdentification(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
+ compilerBuilder.addOptionsModification(
+ options -> {
+ options.apiModelingOptions().enableApiCallerIdentification = true;
+ });
}
- public static class ApiOutliningMethodVerificationHelper {
+ static ApiModelingMethodVerificationHelper verifyThat(TestParameters parameters, Method method) {
+ return new ApiModelingMethodVerificationHelper(parameters, method);
+ }
+
+ public static class ApiModelingMethodVerificationHelper {
private final Method methodOfInterest;
private final TestParameters parameters;
- public ApiOutliningMethodVerificationHelper(
- TestParameters parameters, Method methodOfInterest) {
+ public ApiModelingMethodVerificationHelper(TestParameters parameters, Method methodOfInterest) {
this.methodOfInterest = methodOfInterest;
this.parameters = parameters;
}
protected ThrowingConsumer<CodeInspector, Exception> inlinedIntoFromApiLevel(
Method method, AndroidApiLevel apiLevel) {
- return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevel)
+ return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevel)
? inlinedInto(method)
: notInlinedInto(method);
}
@@ -63,7 +72,7 @@
};
}
- private ThrowingConsumer<CodeInspector, Exception> inlinedInto(Method method) {
+ public ThrowingConsumer<CodeInspector, Exception> inlinedInto(Method method) {
return inspector -> {
MethodSubject candidate = inspector.method(methodOfInterest);
if (!candidate.isPresent()) {
diff --git a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest.java b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest.java
deleted file mode 100644
index 7620ae4..0000000
--- a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright (c) 2021, 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.apioutlining;
-
-import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.verifyThat;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.lang.reflect.Method;
-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 ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest extends TestBase {
-
- private final TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
- }
-
- public ApiOutliningNoInliningOfHigherApiLevelIntoLowerTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test()
- public void testR8() throws Exception {
- Method apiLevel21 = A.class.getDeclaredMethod("apiLevel21");
- Method apiLevel22 = B.class.getDeclaredMethod("apiLevel22");
- R8TestRunResult runResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(Main.class)
- .enableInliningAnnotations()
- .apply(setMockApiLevelForMethod(apiLevel21, AndroidApiLevel.L))
- .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1))
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("A::apiLevel21", "B::apiLevel22");
- if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
- runResult.inspect(
- verifyThat(parameters, apiLevel22)
- .inlinedIntoFromApiLevel(apiLevel21, AndroidApiLevel.L_MR1));
- } else {
- // TODO(b/188388130): Should only inline on minApi >= 22.
- assertThrows(
- AssertionError.class,
- () ->
- runResult.inspect(
- verifyThat(parameters, apiLevel22)
- .inlinedIntoFromApiLevel(apiLevel21, AndroidApiLevel.L_MR1)));
- }
- }
-
- public static class B {
- public static void apiLevel22() {
- System.out.println("B::apiLevel22");
- }
- }
-
- public static class A {
-
- @NeverInline
- public static void apiLevel21() {
- System.out.println("A::apiLevel21");
- B.apiLevel22();
- }
- }
-
- public static class Main {
-
- public static void main(String[] args) {
- A.apiLevel21();
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelTest.java b/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelTest.java
deleted file mode 100644
index 44263ba..0000000
--- a/src/test/java/com/android/tools/r8/apioutlining/ApiOutliningNoInliningOfHigherApiLevelTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) 2021, 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.apioutlining;
-
-import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apioutlining.ApiOutliningTestHelper.verifyThat;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.lang.reflect.Method;
-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 ApiOutliningNoInliningOfHigherApiLevelTest extends TestBase {
-
- private final TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
- }
-
- public ApiOutliningNoInliningOfHigherApiLevelTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void testR8() throws Exception {
- Method minApi = A.class.getDeclaredMethod("minApi");
- Method apiLevel22 = B.class.getDeclaredMethod("apiLevel22");
- R8TestRunResult runResult =
- testForR8(parameters.getBackend())
- .addInnerClasses(getClass())
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(Main.class)
- .enableInliningAnnotations()
- .apply(setMockApiLevelForMethod(apiLevel22, AndroidApiLevel.L_MR1))
- .run(parameters.getRuntime(), Main.class)
- .assertSuccessWithOutputLines("A::minApi", "B::apiLevel22");
- if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)) {
- runResult.inspect(
- verifyThat(parameters, apiLevel22)
- .inlinedIntoFromApiLevel(minApi, AndroidApiLevel.L_MR1));
- } else {
- // TODO(b/188388130): Should only inline on minApi >= 22.
- assertThrows(
- AssertionError.class,
- () ->
- runResult.inspect(
- verifyThat(parameters, apiLevel22)
- .inlinedIntoFromApiLevel(minApi, AndroidApiLevel.L_MR1)));
- }
- }
-
- public static class B {
- public static void apiLevel22() {
- System.out.println("B::apiLevel22");
- }
- }
-
- public static class A {
-
- @NeverInline
- public static void minApi() {
- System.out.println("A::minApi");
- B.apiLevel22();
- }
- }
-
- public static class Main {
-
- public static void main(String[] args) {
- A.minApi();
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index 76ebd58..07ad8f0 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -9,24 +9,35 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
-import java.nio.file.Path;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
public class B77836766 extends TestBase {
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public B77836766(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
/**
* The below Jasmin code mimics the following Kotlin code:
*
@@ -93,15 +104,18 @@
ClassBuilder itf2 = jasminBuilder.addInterface("Itf2");
itf2.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V");
- ClassBuilder cls2 = jasminBuilder.addClass("Cls2", absCls.name, itf2.name);
+ ClassBuilder cls2Class = jasminBuilder.addClass("Cls2", absCls.name, itf2.name);
// Mimic Kotlin's "internal" class
- cls2.setAccess("");
- cls2.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V",
+ cls2Class.setAccess("");
+ cls2Class.addBridgeMethod(
+ "foo",
+ ImmutableList.of("Ljava/lang/Integer;"),
+ "V",
".limit stack 2",
".limit locals 2",
"aload_0",
"aload_1",
- "invokevirtual " + cls2.name + "/foo(Ljava/lang/Object;)V",
+ "invokevirtual " + cls2Class.name + "/foo(Ljava/lang/Object;)V",
"return");
ClassBuilder mainClass = jasminBuilder.addClass("Main");
@@ -115,54 +129,66 @@
"aload_0",
"ldc \"Hello\"",
"invokevirtual " + cls1.name + "/foo(Ljava/lang/String;)V",
- "new " + cls2.name,
+ "new " + cls2Class.name,
"dup",
- "invokespecial " + cls2.name + "/<init>()V",
+ "invokespecial " + cls2Class.name + "/<init>()V",
"astore_0",
"aload_0",
"iconst_0",
"invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;",
- "invokevirtual " + cls2.name + "/foo(Ljava/lang/Integer;)V",
- "return"
- );
+ "invokevirtual " + cls2Class.name + "/foo(Ljava/lang/Integer;)V",
+ "return");
- final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClass.name)
+ .addOptionsModification(this::configure)
+ .noHorizontalClassMerging(cls2Class.name)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject absSubject = inspector.clazz(absCls.name);
+ assertThat(absSubject, isPresent());
+ ClassSubject cls1Subject = inspector.clazz(cls1.name);
+ assertThat(cls1Subject, isPresent());
+ ClassSubject cls2Subject = inspector.clazz(cls2Class.name);
+ assertThat(cls2Subject, isPresent());
- AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+ // Cls1#foo and Cls2#foo should not refer to each other.
+ // They can invoke their own bridge method or AbsCls#foo (via member rebinding).
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject absSubject = inspector.clazz(absCls.name);
- assertThat(absSubject, isPresent());
- ClassSubject cls1Subject = inspector.clazz(cls1.name);
- assertThat(cls1Subject, isPresent());
- ClassSubject cls2Subject = inspector.clazz(cls2.name);
- assertThat(cls2Subject, isPresent());
+ // Cls2#foo has been moved to AbsCls#foo as a result of bridge hoisting.
+ MethodSubject fooInCls2 = cls2Subject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooInCls2, not(isPresent()));
- // Cls1#foo and Cls2#foo should not refer to each other.
- // They can invoke their own bridge method or AbsCls#foo (via member rebinding).
+ MethodSubject fooFromCls2InAbsCls =
+ absSubject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooFromCls2InAbsCls, isPresent());
- // Cls2#foo has been moved to AbsCls#foo as a result of bridge hoisting.
- MethodSubject fooInCls2 = cls2Subject.method("void", "foo", "java.lang.Integer");
- assertThat(fooInCls2, not(isPresent()));
+ // Cls1#foo has been moved to AbsCls#foo as a result of bridge hoisting.
+ MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.String");
+ assertThat(fooInCls1, not(isPresent()));
- MethodSubject fooFromCls2InAbsCls = absSubject.method("void", "foo", "java.lang.Integer");
- assertThat(fooFromCls2InAbsCls, isPresent());
- DexCode code = fooFromCls2InAbsCls.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ MethodSubject fooFromCls1InAbsCls =
+ absSubject.method("void", "foo", "java.lang.String");
+ assertThat(fooFromCls1InAbsCls, isPresent());
- // Cls1#foo has been moved to AbsCls#foo as a result of bridge hoisting.
- MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.String");
- assertThat(fooInCls1, not(isPresent()));
+ if (parameters.isDexRuntime()) {
+ DexCode code = fooFromCls2InAbsCls.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
- MethodSubject fooFromCls1InAbsCls = absSubject.method("void", "foo", "java.lang.String");
- assertThat(fooFromCls1InAbsCls, isPresent());
- code = fooFromCls1InAbsCls.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ code = fooFromCls1InAbsCls.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(absSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ }
+ })
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutput("Hello0");
}
/**
@@ -199,13 +225,17 @@
ClassBuilder itf = jasminBuilder.addInterface("ItfInteger");
itf.addAbstractMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V");
- ClassBuilder cls1 = jasminBuilder.addClass("DerivedInteger", baseCls.name, itf.name);
- cls1.addBridgeMethod("foo", ImmutableList.of("Ljava/lang/Integer;"), "V",
+ ClassBuilder derivedIntegerClass =
+ jasminBuilder.addClass("DerivedInteger", baseCls.name, itf.name);
+ derivedIntegerClass.addBridgeMethod(
+ "foo",
+ ImmutableList.of("Ljava/lang/Integer;"),
+ "V",
".limit stack 2",
".limit locals 2",
"aload_0",
"aload_1",
- "invokevirtual " + cls1.name + "/foo(Ljava/lang/Object;)V",
+ "invokevirtual " + derivedIntegerClass.name + "/foo(Ljava/lang/Object;)V",
"return");
ClassBuilder cls2 = jasminBuilder.addClass("DerivedString", baseCls.name);
@@ -221,14 +251,14 @@
mainClass.addMainMethod(
".limit stack 5",
".limit locals 2",
- "new " + cls1.name,
+ "new " + derivedIntegerClass.name,
"dup",
- "invokespecial " + cls1.name + "/<init>()V",
+ "invokespecial " + derivedIntegerClass.name + "/<init>()V",
"astore_0",
"aload_0",
"iconst_0",
"invokestatic java/lang/Integer/valueOf(I)Ljava/lang/Integer;",
- "invokevirtual " + cls1.name + "/foo(Ljava/lang/Integer;)V",
+ "invokevirtual " + derivedIntegerClass.name + "/foo(Ljava/lang/Integer;)V",
"new " + cls2.name,
"dup",
"invokespecial " + cls2.name + "/<init>()V",
@@ -236,41 +266,51 @@
"aload_0",
"ldc \"Bar\"",
"invokevirtual " + cls2.name + "/bar(Ljava/lang/String;)V",
- "return"
- );
+ "return");
- final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClass.name)
+ .addOptionsModification(this::configure)
+ .noHorizontalClassMerging(derivedIntegerClass.name)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject baseSubject = inspector.clazz(baseCls.name);
+ assertThat(baseSubject, isPresent());
+ ClassSubject cls1Subject = inspector.clazz(derivedIntegerClass.name);
+ assertThat(cls1Subject, isPresent());
+ ClassSubject cls2Subject = inspector.clazz(cls2.name);
+ assertThat(cls2Subject, isPresent());
- AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+ // Cls1#foo and Cls2#bar should refer to Base#foo.
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject baseSubject = inspector.clazz(baseCls.name);
- assertThat(baseSubject, isPresent());
- ClassSubject cls1Subject = inspector.clazz(cls1.name);
- assertThat(cls1Subject, isPresent());
- ClassSubject cls2Subject = inspector.clazz(cls2.name);
- assertThat(cls2Subject, isPresent());
+ MethodSubject barInCls2 = cls2Subject.method("void", "bar", "java.lang.String");
+ assertThat(barInCls2, isPresent());
- // Cls1#foo and Cls2#bar should refer to Base#foo.
+ // Cls1#foo has been moved to Base#foo as a result of bridge hoisting.
+ MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooInCls1, not(isPresent()));
- MethodSubject barInCls2 = cls2Subject.method("void", "bar", "java.lang.String");
- assertThat(barInCls2, isPresent());
- DexCode code = barInCls2.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ MethodSubject fooInBase = baseSubject.method("void", "foo", "java.lang.Integer");
+ assertThat(fooInBase, isPresent());
- // Cls1#foo has been moved to Base#foo as a result of bridge hoisting.
- MethodSubject fooInCls1 = cls1Subject.method("void", "foo", "java.lang.Integer");
- assertThat(fooInCls1, not(isPresent()));
+ if (parameters.isDexRuntime()) {
+ DexCode code = barInCls2.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
- MethodSubject fooInBase = baseSubject.method("void", "foo", "java.lang.Integer");
- assertThat(fooInBase, isPresent());
- code = fooInBase.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ code = fooInBase.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ }
+ })
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutput("0Bar");
}
/**
@@ -337,25 +377,34 @@
"return"
);
- final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClass.name)
+ .addOptionsModification(this::configure)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject baseSubject = inspector.clazz(baseCls.name);
+ assertThat(baseSubject, isPresent());
+ ClassSubject subSubject = inspector.clazz(subCls.name);
+ assertThat(subSubject, isPresent());
- AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
+ // DerivedString2#bar should refer to Base#foo.
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject baseSubject = inspector.clazz(baseCls.name);
- assertThat(baseSubject, isPresent());
- ClassSubject subSubject = inspector.clazz(subCls.name);
- assertThat(subSubject, isPresent());
+ MethodSubject barInSub = subSubject.method("void", "bar", "java.lang.String");
+ assertThat(barInSub, isPresent());
- // DerivedString2#bar should refer to Base#foo.
-
- MethodSubject barInSub = subSubject.method("void", "bar", "java.lang.String");
- assertThat(barInSub, isPresent());
- DexCode code = barInSub.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ if (parameters.isDexRuntime()) {
+ DexCode code = barInSub.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ }
+ })
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutput("0Bar");
}
/*
@@ -413,40 +462,33 @@
"invokevirtual " + cls.name + "/bar(Ljava/lang/String;)V",
"return"
);
- final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
- AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject baseSubject = inspector.clazz(cls.name);
- assertThat(baseSubject, isPresent());
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClass.name)
+ .addOptionsModification(this::configure)
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject baseSubject = inspector.clazz(cls.name);
+ assertThat(baseSubject, isPresent());
- // Base#bar should remain as-is, i.e., refer to Base#foo(Object).
+ // Base#bar should remain as-is, i.e., refer to Base#foo(Object).
- MethodSubject barInSub = baseSubject.method("void", "bar", "java.lang.String");
- assertThat(barInSub, isPresent());
- DexCode code = barInSub.getMethod().getCode().asDexCode();
- checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
- InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
- assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
- }
+ MethodSubject barInSub = baseSubject.method("void", "bar", "java.lang.String");
+ assertThat(barInSub, isPresent());
- private AndroidApp runAndVerifyOnJvmAndArt(
- JasminBuilder jasminBuilder, String mainClassName, String proguardConfig) throws Exception {
- // Run input program on java.
- Path outputDirectory = temp.newFolder().toPath();
- jasminBuilder.writeClassFiles(outputDirectory);
- ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
- assertEquals(0, javaResult.exitCode);
-
- AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig, this::configure);
-
- // Run processed (output) program on ART
- ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
- assertEquals(javaResult.stdout, artResult.stdout);
- assertEquals(-1, artResult.stderr.indexOf("VerifyError"));
-
- return processedApp;
+ if (parameters.isDexRuntime()) {
+ DexCode code = barInSub.getMethod().getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(InvokeVirtual.class, ReturnVoid.class));
+ InvokeVirtual invoke = (InvokeVirtual) code.instructions[0];
+ assertEquals(baseSubject.getDexProgramClass().type, invoke.getMethod().holder);
+ }
+ })
+ .run(parameters.getRuntime(), mainClass.name)
+ .assertSuccessWithOutput("0Bar");
}
private void configure(InternalOptions options) {
diff --git a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
index a8d57c0..4e9353b 100644
--- a/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/bootstrap/BootstrapCurrentEqualityTest.java
@@ -105,7 +105,9 @@
.setMode(mode)
.addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
.addKeepRuleFiles(MAIN_KEEP)
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.apply(c -> FileUtils.writeTextFile(map, c.getProguardMap()))
.writeToZip(jar);
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java
new file mode 100644
index 0000000..e6752f8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StatelessSingletonClassesMergingTest.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import com.android.tools.r8.NeverClassInline;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StatelessSingletonClassesMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public StatelessSingletonClassesMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .noClassStaticizing()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("A", "B");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ A.INSTANCE.f();
+ B.INSTANCE.g();
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ static final A INSTANCE = new A();
+
+ @NeverInline
+ void f() {
+ System.out.println("A");
+ }
+ }
+
+ @NeverClassInline
+ static class B {
+
+ static final B INSTANCE = new B();
+
+ @NeverInline
+ void g() {
+ System.out.println("B");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
index 07cea40..1cd81d7 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ConstructorCantInlineTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.classmerging.horizontal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -30,10 +30,10 @@
.assertSuccessWithOutputLines("c", "foo: foo")
.inspect(
codeInspector -> {
- assertThat(codeInspector.clazz(A.class), not(isPresent()));
- assertThat(codeInspector.clazz(B.class), isPresent());
+ assertThat(codeInspector.clazz(A.class), isAbsent());
+ assertThat(codeInspector.clazz(B.class), isAbsent());
assertThat(codeInspector.clazz(C.class), isPresent());
- assertThat(codeInspector.clazz(D.class), not(isPresent()));
+ assertThat(codeInspector.clazz(D.class), isAbsent());
});
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
index 8a80d6e..6fad862 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InnerOuterClassesTest.java
@@ -41,7 +41,7 @@
.addOptionsModification(
options ->
options.testing.horizontalClassMergingTarget =
- (candidates, target) -> candidates.iterator().next())
+ (appView, candidates, target) -> candidates.iterator().next())
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("a", "b", "c", "d")
.inspect(
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
index d6b6643..7bc50fa 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/InstantiatedAndUninstantiatedClassMergingTest.java
@@ -5,13 +5,13 @@
package com.android.tools.r8.classmerging.horizontal;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
public class InstantiatedAndUninstantiatedClassMergingTest extends HorizontalClassMergingTestBase {
@@ -36,14 +36,14 @@
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
- .addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(
inspector -> {
assertThat(inspector.clazz(Instantiated.class), isPresent());
- assertThat(inspector.clazz(Uninstantiated.class), isPresent());
+ assertThat(
+ inspector.clazz(Uninstantiated.class),
+ notIf(isPresent(), testBuilder.isR8CompatTestBuilder()));
})
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Instantiated", "Uninstantiated");
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
index bf50ab1..0844722 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/NestClassMergingTestRunner.java
@@ -111,7 +111,7 @@
.addOptionsModification(
options -> {
options.testing.horizontalClassMergingTarget =
- (canditates, target) -> {
+ (appView, canditates, target) -> {
Set<ClassReference> candidateClassReferences =
Streams.stream(canditates)
.map(DexClass::getClassReference)
@@ -164,7 +164,7 @@
.addOptionsModification(
options -> {
options.testing.horizontalClassMergingTarget =
- (canditates, target) -> {
+ (appView, canditates, target) -> {
Set<ClassReference> candidateClassReferences =
Streams.stream(canditates)
.map(DexClass::getClassReference)
@@ -217,7 +217,7 @@
.addOptionsModification(
options -> {
options.testing.horizontalClassMergingTarget =
- (canditates, target) -> {
+ (appView, canditates, target) -> {
Set<ClassReference> candidateClassReferences =
Streams.stream(canditates)
.map(DexClass::getClassReference)
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
index 820af37..dd9ab2a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/PackagePrivateMemberAccessTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.classmerging.horizontal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.TestParameters;
@@ -15,6 +15,7 @@
import org.junit.Test;
public class PackagePrivateMemberAccessTest extends HorizontalClassMergingTestBase {
+
public PackagePrivateMemberAccessTest(TestParameters parameters) {
super(parameters);
}
@@ -23,10 +24,8 @@
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
- .addProgramClasses(A.class)
- .addProgramClasses(B.class)
+ .addProgramClasses(A.class, B.class)
.addKeepMainRule(Main.class)
- .allowAccessModification(false)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -34,9 +33,9 @@
.assertSuccessWithOutputLines("foo", "B", "bar", "5", "foobar")
.inspect(
codeInspector -> {
- assertThat(codeInspector.clazz(A.class), isPresent());
- assertThat(codeInspector.clazz(B.class), not(isPresent()));
- assertThat(codeInspector.clazz(C.class), isPresent());
+ assertThat(codeInspector.clazz(A.class), isAbsent());
+ assertThat(codeInspector.clazz(B.class), isAbsent());
+ assertThat(codeInspector.clazz(C.class), isPresent());
});
}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
index 3787201..874c095 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideAbstractMethodWithDefaultTest.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.classmerging.horizontal.dispatch;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -12,7 +14,6 @@
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.classmerging.horizontal.HorizontalClassMergingTestBase;
-import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector;
import org.junit.Test;
public class OverrideAbstractMethodWithDefaultTest extends HorizontalClassMergingTestBase {
@@ -26,21 +27,31 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertIsCompleteMergeGroup(I.class, J.class)
+ .applyIf(
+ !parameters.canUseDefaultAndStaticInterfaceMethods(),
+ i -> i.assertIsCompleteMergeGroup(B1.class, B2.class))
+ .assertNoOtherClassesMerged())
+ .addOptionsModification(
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
- .addHorizontallyMergedClassesInspector(
- HorizontallyMergedClassesInspector::assertNoClassesMerged)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("J", "B2")
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(I.class), isPresent());
- assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(codeInspector.clazz(J.class), isAbsent());
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B1.class), isPresent());
- assertThat(codeInspector.clazz(B2.class), isPresent());
+ assertThat(
+ codeInspector.clazz(B2.class),
+ onlyIf(parameters.canUseDefaultAndStaticInterfaceMethods(), isPresent()));
assertThat(codeInspector.clazz(C1.class), isPresent());
assertThat(codeInspector.clazz(C2.class), isPresent());
});
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
index 580598b..de8d7b2 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultMethodTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.classmerging.horizontal.dispatch;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -25,6 +26,8 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoVerticalClassMergingAnnotations()
@@ -36,9 +39,11 @@
} else {
inspector
.assertClassesNotMerged(A.class, B.class)
+ .assertIsCompleteMergeGroup(I.class, J.class)
.assertIsCompleteMergeGroup(
SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
- SyntheticItemsTestUtils.syntheticCompanionClass(J.class));
+ SyntheticItemsTestUtils.syntheticCompanionClass(J.class))
+ .assertNoOtherClassesMerged();
}
})
.run(parameters.getRuntime(), Main.class)
@@ -46,7 +51,9 @@
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(I.class), isPresent());
- assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(
+ codeInspector.clazz(J.class),
+ onlyIf(parameters.canUseDefaultAndStaticInterfaceMethods(), isPresent()));
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B.class), isPresent());
assertThat(codeInspector.clazz(C.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
index 65ca772..56be50b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/dispatch/OverrideDefaultOnSuperMethodTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.classmerging.horizontal.dispatch;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.onlyIf;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverClassInline;
@@ -30,6 +31,8 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
@@ -42,9 +45,11 @@
} else {
inspector
.assertClassesNotMerged(A.class, B.class)
+ .assertIsCompleteMergeGroup(I.class, J.class)
.assertIsCompleteMergeGroup(
SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
- SyntheticItemsTestUtils.syntheticCompanionClass(J.class));
+ SyntheticItemsTestUtils.syntheticCompanionClass(J.class))
+ .assertNoOtherClassesMerged();
}
})
.run(parameters.getRuntime(), Main.class)
@@ -52,7 +57,9 @@
.inspect(
codeInspector -> {
assertThat(codeInspector.clazz(I.class), isPresent());
- assertThat(codeInspector.clazz(J.class), isPresent());
+ assertThat(
+ codeInspector.clazz(J.class),
+ onlyIf(parameters.canUseDefaultAndStaticInterfaceMethods(), isPresent()));
assertThat(codeInspector.clazz(Parent.class), isPresent());
assertThat(codeInspector.clazz(A.class), isPresent());
assertThat(codeInspector.clazz(B.class), isPresent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java
index 650d804..4d81f1a 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/ClassHierarchyCycleAfterMergingTest.java
@@ -44,11 +44,6 @@
// hierarchy.
.addHorizontallyMergedClassesInspector(
HorizontallyMergedClassesInspector::assertNoClassesMerged)
- .addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
.enableNoHorizontalClassMergingAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
index 1c5d915..c1912b5 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -16,8 +15,9 @@
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -25,19 +25,21 @@
@RunWith(Parameterized.class)
public class CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest extends TestBase {
+ private final boolean enableInterfaceMergingInInitial;
private final TestParameters parameters;
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameterized.Parameters(name = "{1}, enableInterfaceMergingInInitial: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
public CollisionWithDefaultMethodOutsideMergeGroupAfterSubclassMergingTest(
- TestParameters parameters) {
+ boolean enableInterfaceMergingInInitial, TestParameters parameters) {
+ this.enableInterfaceMergingInInitial = enableInterfaceMergingInInitial;
this.parameters = parameters;
}
- // TODO(b/173990042): Disallow merging of A and B in the first round of class merging.
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
@@ -48,23 +50,23 @@
// the default method J.m() to A.
.addHorizontallyMergedClassesInspector(
inspector -> {
+ inspector
+ .assertIsCompleteMergeGroup(A.class, B.class)
+ .assertMergedInto(B.class, A.class);
if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
- inspector
- .assertIsCompleteMergeGroup(A.class, B.class)
- .assertMergedInto(B.class, A.class)
- .assertClassesNotMerged(I.class, J.class, K.class);
+ inspector.assertClassesNotMerged(I.class, J.class, K.class);
} else {
inspector
- .assertIsCompleteMergeGroup(A.class, B.class)
- .assertMergedInto(B.class, A.class)
.assertIsCompleteMergeGroup(I.class, J.class)
.assertClassesNotMerged(K.class);
}
})
.addOptionsModification(
options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
+ if (enableInterfaceMergingInInitial) {
+ options.horizontalClassMergerOptions().setEnableInterfaceMergingInInitial();
+ }
+ options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal();
})
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
@@ -80,10 +82,10 @@
assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
assertThat(aClassSubject, isImplementing(inspector.clazz(K.class)));
- ClassSubject bClassSubject = inspector.clazz(C.class);
- assertThat(bClassSubject, isPresent());
+ ClassSubject cClassSubject = inspector.clazz(C.class);
+ assertThat(cClassSubject, isPresent());
assertThat(
- bClassSubject,
+ cClassSubject,
isImplementing(
inspector.clazz(
parameters.canUseDefaultAndStaticInterfaceMethods()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
index d4f14bd..1c64d9d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupClassTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -52,10 +51,7 @@
}
})
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
index f9e85ba..3a33b0f 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/CollisionWithDefaultMethodOutsideMergeGroupLambdaTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -53,11 +52,7 @@
}
})
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- options.horizontalClassMergerOptions().setIgnoreRuntimeTypeChecksForTesting();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
index 7ca9cc5..c28bf26 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesMergingTest.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
-import static org.junit.Assert.assertFalse;
-
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -37,10 +35,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
index dad75a4..8cfb7af 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithIntersectionMergingTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -37,10 +36,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
index e58ca16..13940fe 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentParametersMergingTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -39,10 +38,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
index a877121..3236862 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointFunctionalInterfacesWithSameNameAndDifferentReturnTypeMergingTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -39,10 +38,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
index 0807547..2d1ac21 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithDefaultMethodsMergingTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -43,10 +42,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
index 5f30eff..7ca2089 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/DisjointInterfacesWithoutDefaultMethodsMergingTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -43,10 +42,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java
new file mode 100644
index 0000000..d059bbf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfaceChainMergingTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging.horizontal.interfaces;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EmptyInterfaceChainMergingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EmptyInterfaceChainMergingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector
+ .assertIsCompleteMergeGroup(I.class, J.class, K.class)
+ .assertNoOtherClassesMerged())
+ .addOptionsModification(
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject, isImplementing(inspector.clazz(I.class)));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccess();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(A.class);
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface I {}
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface J extends I {}
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface K extends J {}
+
+ static class A implements K {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
index 24e5de8..e2b196c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/EmptyInterfacesMergingTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -41,10 +40,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
index 2d264db..100c54b 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesMergingTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -37,10 +36,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
index 23ed1ea..645a0c8 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/IdenticalFunctionalInterfacesWithIntersectionMergingTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.classmerging.horizontal.interfaces;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
@@ -37,10 +36,7 @@
.addHorizontallyMergedClassesInspector(
inspector -> inspector.assertIsCompleteMergeGroup(I.class, J.class))
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.noClassInliningOfSynthetics()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
index 6ad6ef1..2284113 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/interfaces/NoDefaultMethodMergingTest.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isImplementing;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertFalse;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
@@ -51,10 +50,7 @@
}
})
.addOptionsModification(
- options -> {
- assertFalse(options.horizontalClassMergerOptions().isInterfaceMergingEnabled());
- options.horizontalClassMergerOptions().enableInterfaceMerging();
- })
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
index 1c33206..392c587 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontalstatic/StaticClassMergerInterfaceTest.java
@@ -40,19 +40,21 @@
testForR8(parameters.getBackend())
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
- // TODO(b/173990042): Extend horizontal class merging to interfaces.
.addHorizontallyMergedClassesInspector(
inspector -> {
if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
- inspector.assertNoClassesMerged();
+ inspector.assertIsCompleteMergeGroup(I.class, J.class).assertNoOtherClassesMerged();
} else {
inspector
+ .assertClassesNotMerged(I.class, J.class)
.assertClassReferencesMerged(
SyntheticItemsTestUtils.syntheticCompanionClass(I.class),
SyntheticItemsTestUtils.syntheticCompanionClass(J.class))
- .assertClassesNotMerged(I.class, J.class);
+ .assertNoOtherClassesMerged();
}
})
+ .addOptionsModification(
+ options -> options.horizontalClassMergerOptions().setEnableInterfaceMergingInFinal())
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
@@ -62,11 +64,9 @@
// We do not allow horizontal class merging of interfaces and classes. Therefore, A
// should remain in the output.
assertThat(inspector.clazz(A.class), isPresent());
-
- // TODO(b/173990042): I and J should be merged.
if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
assertThat(inspector.clazz(I.class), isPresent());
- assertThat(inspector.clazz(J.class), isPresent());
+ assertThat(inspector.clazz(J.class), isAbsent());
} else {
assertThat(inspector.clazz(syntheticCompanionClass(I.class)), isPresent());
assertThat(inspector.clazz(syntheticCompanionClass(J.class)), isAbsent());
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 5468cae..2f157d0 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -1052,7 +1052,8 @@
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever.class"),
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$SimpleInterfaceImpl.class"),
CF_DIR.resolve("pkg/SimpleInterfaceImplRetriever$1.class"),
- CF_DIR.resolve("NeverInline.class")
+ CF_DIR.resolve("NeverInline.class"),
+ CF_DIR.resolve("NoHorizontalClassMerging.class")
};
// SimpleInterface cannot be merged into SimpleInterfaceImpl because SimpleInterfaceImpl
// is in a different package and is not public.
diff --git a/src/test/java/com/android/tools/r8/debug/ContinuousKotlinSteppingTest.java b/src/test/java/com/android/tools/r8/debug/ContinuousKotlinSteppingTest.java
index 539af51..c3e45b6 100644
--- a/src/test/java/com/android/tools/r8/debug/ContinuousKotlinSteppingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ContinuousKotlinSteppingTest.java
@@ -17,7 +17,7 @@
private static final String MAIN_METHOD_NAME = "main";
- @Parameters(name = "{0}")
+ @Parameters(name = "{0}, {1}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters().withDexRuntimes().withAllApiLevels().build(),
diff --git a/src/test/java/com/android/tools/r8/debug/DoNotCrashOnAccessToThisRunner.java b/src/test/java/com/android/tools/r8/debug/DoNotCrashOnAccessToThisRunner.java
index 3e562cf..2363533 100644
--- a/src/test/java/com/android/tools/r8/debug/DoNotCrashOnAccessToThisRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/DoNotCrashOnAccessToThisRunner.java
@@ -27,17 +27,19 @@
DelayedDebugTestConfig cf =
temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
DelayedDebugTestConfig d8 =
- temp -> new D8DebugTestConfig().compileAndAdd(
- temp,
- ImmutableList.of(ToolHelper.getClassFileForTestClass(CLASS)),
- options -> {
- // Release mode so receiver can be clobbered.
- options.debug = false;
- // Api level M so that the workarounds for Lollipop verifier doesn't
- // block the receiver register. We want to check b/116683601 which
- // happens on at least 7.0.0.
- options.minApiLevel = AndroidApiLevel.M.getLevel();
- });
+ temp ->
+ new D8DebugTestConfig()
+ .compileAndAdd(
+ temp,
+ ImmutableList.of(ToolHelper.getClassFileForTestClass(CLASS)),
+ options -> {
+ // Release mode so receiver can be clobbered.
+ options.debug = false;
+ // Api level M so that the workarounds for Lollipop verifier doesn't
+ // block the receiver register. We want to check b/116683601 which
+ // happens on at least 7.0.0.
+ options.minApiLevel = AndroidApiLevel.M;
+ });
return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
index 72549c5..9691b07 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
@@ -29,10 +29,10 @@
@RunWith(Parameterized.class)
public class DesugarLambdaWithAnonymousClass extends TestBase {
- private List<String> EXPECTED_JAVAC_RESULT =
+ private final List<String> EXPECTED_JAVAC_RESULT =
ImmutableList.of("Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
- private List<String> EXPECTED_D8_DESUGARED_RESULT =
+ private final List<String> EXPECTED_D8_DESUGARED_RESULT =
ImmutableList.of(
"Hello from inside lambda$test$0$DesugarLambdaWithAnonymousClass$TestClass",
"Hello from inside lambda$testStatic$1");
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 e4dd6d9..7f77265 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
@@ -4,14 +4,18 @@
package com.android.tools.r8.desugar.backports;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static java.util.stream.Collectors.toList;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestBuilder;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -148,7 +152,23 @@
.setMinApi(parameters.getApiLevel())
.apply(this::configureProgram)
.setIncludeClassesChecksum(true)
- .compile()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (diagnostics.getWarnings().isEmpty()) {
+ diagnostics.assertNoMessages();
+ return;
+ }
+ // When compiling with an old android.jar some tests refer to non-present types.
+ // Check only java.util types are missing and that none of them are about the target
+ // type that is being backported.
+ diagnostics
+ .assertOnlyWarnings()
+ .assertAllWarningsMatch(
+ diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class))
+ .assertAllWarningsMatch(diagnosticMessage(containsString("java.util")))
+ .assertNoWarningsMatch(
+ diagnosticMessage(containsString(targetClass.getName())));
+ })
.run(parameters.getRuntime(), testClassName)
.assertSuccess()
.inspect(this::assertDesugaring);
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
index 8096c47..5cfc6cd 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/TestBackportedNotPresentInAndroidJar.java
@@ -39,7 +39,7 @@
// android.jar for that level.
CodeInspector inspector = new CodeInspector(ToolHelper.getAndroidJar(apiLevel));
InternalOptions options = new InternalOptions();
- options.minApiLevel = apiLevel.getLevel();
+ options.minApiLevel = apiLevel;
List<DexMethod> backportedMethods =
BackportedMethodRewriter.generateListOfBackportedMethods(
AndroidApp.builder().build(), options, ThreadUtils.getExecutorService(options));
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PriorityQueueSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PriorityQueueSubclassTest.java
new file mode 100644
index 0000000..2c29f58
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PriorityQueueSubclassTest.java
@@ -0,0 +1,86 @@
+// 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import java.util.PriorityQueue;
+import java.util.stream.Stream;
+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 PriorityQueueSubclassTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ }
+
+ public PriorityQueueSubclassTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testPriorityQueueD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(PriorityQueueSubclassTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines("1");
+ }
+
+ @Test
+ public void testPriorityQueueR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(Backend.DEX)
+ .addInnerClasses(PriorityQueueSubclassTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepClassAndMembersRules(Executor.class)
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Executor.class)
+ .assertSuccessWithOutputLines("1");
+ }
+
+ static class Executor {
+
+ public static void main(String[] args) {
+ MyPriorityQueue strings = new MyPriorityQueue();
+ strings.add("1");
+ strings.add("2");
+ System.out.println(strings.iterator().next());
+ }
+ }
+
+ static class MyPriorityQueue extends PriorityQueue<String> {
+
+ @Override
+ public Stream<String> stream() {
+ return Stream.empty();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
index ef8f61a..55e2905 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/kotlin/KotlinMetadataTest.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
@@ -130,7 +131,7 @@
.addKeepAllClassesRule()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.setMinApi(parameters.getApiLevel())
- .allowDiagnosticWarningMessages()
+ .allowDiagnosticMessages()
.allowUnusedDontWarnKotlinReflectJvmInternal(
kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72));
KeepRuleConsumer keepRuleConsumer = null;
@@ -141,6 +142,8 @@
R8TestCompileResult compileResult =
testBuilder
.compile()
+ .assertNoErrorMessages()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib);
if (desugarLibrary) {
assertNotNull(keepRuleConsumer);
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
index 1eac80e..de1c949 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8BootstrapTest.java
@@ -77,7 +77,9 @@
options.desugarState = DesugarState.ON;
options.cfToCfDesugar = true;
}))
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.inspect(inspector -> assertNests(inspector, desugar))
.writeToZip();
}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
index cb549f5..31f7aba 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11R8CompilationTest.java
@@ -50,7 +50,9 @@
.setMinApi(parameters.getApiLevel())
.addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_11_JAR)
.addKeepRuleFiles(MAIN_KEEP)
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.inspect(this::assertNotEmpty)
.inspect(Java11R8CompilationTest::assertNoNests);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
index b54a7d5..35e52b4 100644
--- a/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/staticinterfacemethod/InvokeStaticDesugarTest.java
@@ -15,12 +15,12 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.IntBox;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Collection;
@@ -149,8 +149,8 @@
return box.get();
}
- private Set<MethodReference> getSyntheticMethods(CodeInspector inspector) {
- Set<MethodReference> methods = new HashSet<>();
+ private Set<FoundMethodSubject> getSyntheticMethods(CodeInspector inspector) {
+ Set<FoundMethodSubject> methods = new HashSet<>();
assert inspector.allClasses().stream()
.allMatch(
c ->
@@ -159,10 +159,7 @@
c.getFinalReference()));
inspector.allClasses().stream()
.filter(c -> SyntheticItemsTestUtils.isExternalStaticInterfaceCall(c.getFinalReference()))
- .forEach(
- c ->
- c.allMethods(m -> !m.isInstanceInitializer())
- .forEach(m -> methods.add(m.asMethodReference())));
+ .forEach(c -> methods.addAll(c.allMethods(m -> !m.isInstanceInitializer())));
return methods;
}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
index 1bfbaac..71ddab8 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -63,7 +63,7 @@
.addOptionsModification(
options ->
options.testing.horizontalClassMergingTarget =
- (candidates, target) -> candidates.iterator().next())
+ (appView, candidates, target) -> candidates.iterator().next())
.addHorizontallyMergedClassesInspector(
inspector ->
inspector.assertMergedInto(BaseWithStatic.class, AFeatureWithStatic.class))
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java
index f4adbfb..3cb6899 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureIdentityTest.java
@@ -78,6 +78,10 @@
if (signature == null) {
return;
}
+ if (signature.equals(
+ "<R::Lcom/android/tools/r8/synthesis/Rewritable<TR;>;>Ljava/lang/Object;")) {
+ int xyz = 0;
+ }
TestDiagnosticMessagesImpl testDiagnosticMessages = new TestDiagnosticMessagesImpl();
ClassSignature classSignature =
GenericSignature.parseClassSignature(
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
index 65b434b..969a59f 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureCorrectnessHelperTests.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder;
import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper.SignatureEvaluationResult;
import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
@@ -44,9 +45,10 @@
buildInnerClasses(GenericSignatureCorrectnessHelperTests.class)
.addLibraryFile(ToolHelper.getJava8RuntimeJar())
.build());
- GenericSignatureCorrectnessHelper check =
- GenericSignatureCorrectnessHelper.createForVerification(appView);
- check.run();
+ GenericSignatureContextBuilder contextBuilder =
+ GenericSignatureContextBuilder.create(appView.appInfo().classes());
+ GenericSignatureCorrectnessHelper.createForVerification(appView, contextBuilder)
+ .run(appView.appInfo().classes());
}
@Test
@@ -139,7 +141,7 @@
})
.transform()),
ClassWithInvalidArgumentCount.class,
- SignatureEvaluationResult.INVALID_APPLICATION_COUNT);
+ SignatureEvaluationResult.VALID);
}
@Test
@@ -182,8 +184,10 @@
.addClassProgramData(transformations)
.addLibraryFile(ToolHelper.getJava8RuntimeJar())
.build());
+ GenericSignatureContextBuilder contextBuilder =
+ GenericSignatureContextBuilder.create(appView.appInfo().classes());
GenericSignatureCorrectnessHelper check =
- GenericSignatureCorrectnessHelper.createForInitialCheck(appView);
+ GenericSignatureCorrectnessHelper.createForInitialCheck(appView, contextBuilder);
DexProgramClass clazz =
appView
.definitionFor(
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
index 64f2ac3..1fb82ab 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
@@ -13,12 +13,16 @@
import com.android.tools.r8.TestDiagnosticMessagesImpl;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GenericSignature;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import com.android.tools.r8.graph.GenericSignatureContextBuilder.TypeParameterContext;
import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.BiPredicateUtils;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
@@ -48,7 +52,7 @@
}
@Test
- public void testVariablesInOuterPosition() {
+ public void testVariablesInOuterPosition() throws Exception {
runTest(
ImmutableMap.of("T", objectType, "R", objectType),
Collections.emptySet(),
@@ -60,7 +64,7 @@
}
@Test
- public void testVariablesInInnerPosition() {
+ public void testVariablesInInnerPosition() throws Exception {
runTest(
ImmutableMap.of("T", objectType, "R", objectType),
Collections.emptySet(),
@@ -72,7 +76,7 @@
}
@Test
- public void testRemovingPrunedLink() {
+ public void testRemovingPrunedLink() throws Exception {
runTest(
Collections.emptyMap(),
Collections.emptySet(),
@@ -85,7 +89,7 @@
}
@Test
- public void testRemovedGenericArguments() {
+ public void testRemovedGenericArguments() throws Exception {
runTest(
Collections.emptyMap(),
Collections.emptySet(),
@@ -102,11 +106,17 @@
BiPredicate<DexType, DexType> removedLink,
Predicate<DexType> hasFormalTypeParameters,
String initialSignature,
- String expectedRewrittenSignature) {
+ String expectedRewrittenSignature)
+ throws Exception {
+ AppView<AppInfo> appView = computeAppView(AndroidApp.builder().build());
GenericSignaturePartialTypeArgumentApplier argumentApplier =
GenericSignaturePartialTypeArgumentApplier.build(
- objectType, removedLink, hasFormalTypeParameters)
- .addSubstitutionsAndVariables(substitutions, liveVariables);
+ appView,
+ TypeParameterContext.empty()
+ .addPrunedSubstitutions(substitutions)
+ .addLiveParameters(liveVariables),
+ removedLink,
+ hasFormalTypeParameters);
TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
MethodTypeSignature methodTypeSignature =
argumentApplier.visitMethodSignature(
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
index 58812ee..80939de 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/UnboundedFormalTypeGenericSignatureTest.java
@@ -6,7 +6,6 @@
import static org.hamcrest.CoreMatchers.containsString;
-import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -87,53 +86,51 @@
@Test
public void testUnboundParametersInClassR8() throws Exception {
- R8TestRunResult runResult =
- testForR8(parameters.getBackend())
- .addProgramClassFileData(
- transformer(Main.class)
- .removeInnerClasses()
- .setGenericSignature("L" + SUPER_BINARY_NAME + "<TR;>;")
- .transform(),
- transformer(Super.class).removeInnerClasses().transform())
- .addKeepAllClassesRule()
- .addKeepAttributes(
- ProguardKeepAttributes.SIGNATURE,
- ProguardKeepAttributes.INNER_CLASSES,
- ProguardKeepAttributes.ENCLOSING_METHOD)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), Main.class);
- if (parameters.isCfRuntime()) {
- runResult.assertFailureWithErrorThatMatches(containsString("java.lang.NullPointerException"));
- } else {
- runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<R>", "R", "T");
- }
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(Main.class)
+ .removeInnerClasses()
+ .setGenericSignature("L" + SUPER_BINARY_NAME + "<TR;>;")
+ .transform(),
+ transformer(Super.class).removeInnerClasses().transform())
+ .addKeepAllClassesRule()
+ .addKeepAttributes(
+ ProguardKeepAttributes.SIGNATURE,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticInfoMessages()
+ .compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "class " + Super.class.getTypeName(), "R", "class java.lang.Object");
}
@Test
public void testUnboundParametersInMethodR8() throws Exception {
- R8TestRunResult runResult =
- testForR8(parameters.getBackend())
- .addProgramClassFileData(
- transformer(Main.class)
- .removeInnerClasses()
- .setGenericSignature(
- MethodPredicate.onName("testStatic"), "<R:Ljava/lang/Object;>()TS;")
- .setGenericSignature(
- MethodPredicate.onName("testVirtual"), "<R:Ljava/lang/Object;>()TQ;")
- .transform(),
- transformer(Super.class).removeInnerClasses().transform())
- .addKeepAllClassesRule()
- .addKeepAttributes(
- ProguardKeepAttributes.SIGNATURE,
- ProguardKeepAttributes.INNER_CLASSES,
- ProguardKeepAttributes.ENCLOSING_METHOD)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), Main.class);
- if (parameters.isCfRuntime()) {
- runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "null", "null");
- } else {
- runResult.assertSuccessWithOutputLines(Super.class.getTypeName() + "<T>", "S", "Q");
- }
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ transformer(Main.class)
+ .removeInnerClasses()
+ .setGenericSignature(
+ MethodPredicate.onName("testStatic"), "<R:Ljava/lang/Object;>()TS;")
+ .setGenericSignature(
+ MethodPredicate.onName("testVirtual"), "<R:Ljava/lang/Object;>()TQ;")
+ .transform(),
+ transformer(Super.class).removeInnerClasses().transform())
+ .addKeepAllClassesRule()
+ .addKeepAttributes(
+ ProguardKeepAttributes.SIGNATURE,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticInfoMessages()
+ .compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ Super.class.getTypeName() + "<T>", "class java.lang.Object", "class java.lang.Object");
}
public static class Super<T> {}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java
index 4c2ac5e..cf67f18 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/UnknownClassInSignatureTest.java
@@ -7,6 +7,7 @@
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.assertNull;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -27,7 +28,6 @@
public class UnknownClassInSignatureTest extends TestBase {
private final TestParameters parameters;
- private final String NEW_CLASS_SIGNATURE = "<T:LUnknownClass1;>LUnknownClass2<LUnknownClass3;>;";
private final String NEW_FIELD_SIGNATURE = "LUnknownClass4<LUnknownClass4;>;";
private final String NEW_METHOD_SIGNATURE = "()LUnkownClass5<LunknownPackage/UnknownClass6;>;";
@@ -46,7 +46,7 @@
.addProgramClassFileData(
transformer(Main.class)
.removeInnerClasses()
- .setGenericSignature(NEW_CLASS_SIGNATURE)
+ .setGenericSignature("<T:LUnknownClass1;>LUnknownClass2<LUnknownClass3;>;")
.setGenericSignature(FieldPredicate.onName("field"), NEW_FIELD_SIGNATURE)
.setGenericSignature(MethodPredicate.onName("main"), NEW_METHOD_SIGNATURE)
.transform())
@@ -56,13 +56,16 @@
ProguardKeepAttributes.ENCLOSING_METHOD,
ProguardKeepAttributes.INNER_CLASSES)
.setMinApi(parameters.getApiLevel())
+ .allowDiagnosticInfoMessages()
+ .compile()
+ .apply(TestBase::verifyExpectedInfoFromGenericSignatureSuperTypeValidation)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("Hello World!")
.inspect(
inspector -> {
ClassSubject clazz = inspector.clazz(Main.class);
assertThat(clazz, isPresent());
- assertEquals(NEW_CLASS_SIGNATURE, clazz.getFinalSignatureAttribute());
+ assertNull(clazz.getFinalSignatureAttribute());
FieldSubject field = clazz.uniqueFieldWithFinalName("field");
assertThat(field, isPresent());
assertEquals(NEW_FIELD_SIGNATURE, field.getFinalSignatureAttribute());
diff --git a/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
index 82570e9..183031e 100644
--- a/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
+++ b/src/test/java/com/android/tools/r8/internal/ClankDepsTest.java
@@ -3,8 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.proguardConfigurationRuleDoesNotMatch;
+import static com.android.tools.r8.utils.codeinspector.Matchers.typeVariableNotInScope;
+import static org.hamcrest.CoreMatchers.anyOf;
+
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -45,6 +48,10 @@
.allowUnusedProguardConfigurationRules()
.allowUnnecessaryDontWarnWildcards()
.setMinApi(AndroidApiLevel.N)
- .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertOnlyInfos);
+ .allowDiagnosticInfoMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertAllInfosMatch(
+ anyOf(typeVariableNotInScope(), proguardConfigurationRuleDoesNotMatch())));
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail17060416TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail17060416TreeShakeJarVerificationTest.java
index 99237cf..7319852 100644
--- a/src/test/java/com/android/tools/r8/internal/Gmail17060416TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/Gmail17060416TreeShakeJarVerificationTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.internal;
import static com.android.tools.r8.ToolHelper.isLocalDevelopment;
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -20,16 +22,14 @@
public class Gmail17060416TreeShakeJarVerificationTest extends GmailCompilationBase {
private static final int MAX_SIZE = 20000000;
- private final TestParameters parameters;
-
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().build();
+ return getTestParameters().withNoneRuntime().build();
}
public Gmail17060416TreeShakeJarVerificationTest(TestParameters parameters) {
super(170604, 16);
- this.parameters = parameters;
+ assert parameters.isNoneRuntime();
}
@Test
@@ -37,13 +37,24 @@
assumeTrue(isLocalDevelopment());
R8TestCompileResult compileResult =
- testForR8(parameters.getBackend())
+ testForR8(Backend.DEX)
.addKeepRuleFiles(Paths.get(base).resolve(BASE_PG_CONF))
+ .allowDiagnosticMessages()
+ .allowUnusedDontWarnPatterns()
.allowUnusedProguardConfigurationRules()
- .compile();
+ .compile()
+ .assertAllInfoMessagesMatch(
+ anyOf(
+ containsString("Ignoring option: -optimizations"),
+ containsString("Proguard configuration rule does not match anything"),
+ containsString("Invalid signature"),
+ containsString("Validation error: A type variable is not in scope")))
+ .assertAllWarningMessagesMatch(
+ anyOf(
+ containsString("Ignoring option: -outjars"),
+ containsString("Cannot determine what identifier string flows to")));
int appSize = compileResult.app.applicationSize();
assertTrue("Expected max size of " + MAX_SIZE+ ", got " + appSize, appSize < MAX_SIZE);
}
-
}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
index 12236fc..4e784a3 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
@@ -8,6 +8,9 @@
import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepAllProtosRule;
import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepDynamicMethodSignatureRule;
import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepNewMessageInfoSignatureRule;
+import static com.android.tools.r8.utils.codeinspector.Matchers.proguardConfigurationRuleDoesNotMatch;
+import static com.android.tools.r8.utils.codeinspector.Matchers.typeVariableNotInScope;
+import static org.hamcrest.CoreMatchers.anyOf;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestParameters;
@@ -49,7 +52,12 @@
.allowUnusedProguardConfigurationRules()
.enableProtoShrinking(false)
.setMinApi(AndroidApiLevel.N)
+ .allowDiagnosticInfoMessages()
.compile()
+ .inspectDiagnosticMessages(
+ diagnostics ->
+ diagnostics.assertAllInfosMatch(
+ anyOf(typeVariableNotInScope(), proguardConfigurationRuleDoesNotMatch())))
.inspect(this::inspect);
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerWithInstancePutCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerWithInstancePutCanBePostponedTest.java
new file mode 100644
index 0000000..8d3cae4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerWithInstancePutCanBePostponedTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, 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.analysis.sideeffect;
+
+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.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.analysis.sideeffect.SingletonClassInitializerPatternCanBePostponedTest.A;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 SingletonClassInitializerWithInstancePutCanBePostponedTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SingletonClassInitializerWithInstancePutCanBePostponedTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableMemberValuePropagationAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(A.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject.uniqueFieldWithName("INSTANCE"), isPresent());
+
+ // A.inlineable() should be inlined, but we should not synthesize an $r8$clinit field.
+ assertThat(classSubject.uniqueMethodWithName("inlineable"), not(isPresent()));
+ assertEquals(2, classSubject.allStaticFields().size());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ A.inlineable();
+ System.out.println(A.getInstance().getMessage());
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private static A INSTANCE;
+
+ static {
+ A a = new A();
+ a.message = " world!";
+ INSTANCE = a;
+ }
+
+ @NeverPropagateValue private String message;
+
+ static void inlineable() {
+ System.out.print("Hello");
+ }
+
+ @NeverInline
+ static A getInstance() {
+ return INSTANCE;
+ }
+
+ @NeverInline
+ String getMessage() {
+ return message;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerWithInstancePutCannotBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerWithInstancePutCannotBePostponedTest.java
new file mode 100644
index 0000000..5914a59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/SingletonClassInitializerWithInstancePutCannotBePostponedTest.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2021, 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.analysis.sideeffect;
+
+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.Assert.assertEquals;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverPropagateValue;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SingletonClassInitializerWithInstancePutCannotBePostponedTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public SingletonClassInitializerWithInstancePutCannotBePostponedTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableMemberValuePropagationAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(A.class);
+ assertThat(classSubject, isPresent());
+ assertThat(classSubject.uniqueFieldWithName("INSTANCE"), isPresent());
+
+ // A.inlineable() should be inlined, but we should synthesize an $r8$clinit field.
+ assertThat(classSubject.uniqueMethodWithName("inlineable"), not(isPresent()));
+ assertEquals(2, classSubject.allStaticFields().size());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Environment.data = " world!";
+ // Triggers A.<clinit>(), which sets A.INSTANCE to a new instance with A.message=" world".
+ A.inlineable();
+ // Unset Environment.data, such that the following call to println() prints null if we failed
+ // to trigger A.<clinit>() above.
+ Environment.data = null;
+ System.out.println(A.getInstance().getMessage());
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ private static A INSTANCE;
+
+ static {
+ A a = new A();
+ a.message = Environment.data;
+ INSTANCE = a;
+ }
+
+ @NeverPropagateValue private String message;
+
+ static void inlineable() {
+ System.out.print("Hello");
+ }
+
+ @NeverInline
+ static A getInstance() {
+ return INSTANCE;
+ }
+
+ @NeverInline
+ String getMessage() {
+ return message;
+ }
+ }
+
+ static class Environment {
+
+ static String data;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java
index a8ef7a1..4431c5a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastInterfaceArrayTest.java
@@ -15,7 +15,7 @@
@RunWith(Parameterized.class)
public class CheckCastInterfaceArrayTest extends TestBase {
- private static String EXPECTED = StringUtils.lines("A", "B");
+ private static final String EXPECTED = StringUtils.lines("A", "B");
private final TestParameters parameters;
@@ -30,18 +30,10 @@
@Test
public void testJvmAndD8() throws Exception {
- if (parameters.isCfRuntime()) {
- testForJvm()
- .addInnerClasses(CheckCastInterfaceArrayTest.class)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- } else {
- testForD8()
- .addInnerClasses(CheckCastInterfaceArrayTest.class)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(EXPECTED);
- }
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, A.class, B.class, TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
}
@Test
@@ -58,6 +50,17 @@
.assertSuccessWithOutput(EXPECTED);
}
+ @Test
+ public void testJvmAndD8Throwing() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(I.class, A.class, B.class, TestClassError.class)
+ .run(parameters.getRuntime(), TestClassError.class)
+ .applyIf(
+ parameters.isDexRuntime(),
+ result -> result.assertFailureWithErrorThatThrows(VerifyError.class),
+ result -> result.assertSuccessWithOutput(EXPECTED));
+ }
+
interface I {
int id();
}
@@ -98,4 +101,21 @@
}
}
}
+
+ static class TestClassError {
+
+ public static I[] get(I kind) {
+ // Work around the ART bug by inserting an explicit check cast (fortunately javac keeps it).
+ return (kind.id() == A.id ? A.values() : B.values());
+ }
+
+ public static void main(String[] args) {
+ for (I i : get(A.A)) {
+ System.out.println(i);
+ }
+ for (I i : get(B.B)) {
+ System.out.println(i);
+ }
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
index 8cf8b26..b4d7969 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
@@ -27,7 +27,7 @@
testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
InternalOptions options = new InternalOptions();
- options.minApiLevel = AndroidApiLevel.I_MR1.getLevel();
+ options.minApiLevel = AndroidApiLevel.I_MR1;
assert options.canHaveArtCheckCastVerifierBug();
testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ZipFileInstanceOfAutoCloseableTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ZipFileInstanceOfAutoCloseableTest.java
index b15cca9..14d84e7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ZipFileInstanceOfAutoCloseableTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/ZipFileInstanceOfAutoCloseableTest.java
@@ -103,7 +103,7 @@
assumeTrue(parameters.isDexRuntime());
// Set the min API and create the raw app.
InternalOptions options = new InternalOptions();
- options.minApiLevel = parameters.getApiLevel().getLevel();
+ options.minApiLevel = parameters.getApiLevel();
DirectMappedDexApplication application =
new ApplicationReader(
AndroidApp.builder()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWitNonIntermediateIdValueTest.java b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWitNonIntermediateIdValueTest.java
new file mode 100644
index 0000000..213f02b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/switches/StringSwitchWitNonIntermediateIdValueTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2021, 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.switches;
+
+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.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+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.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringSwitchWitNonIntermediateIdValueTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public StringSwitchWitNonIntermediateIdValueTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(Main.class)
+ .addOptionsModification(options -> options.minimumStringSwitchSize = 4)
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::verifyRewrittenToIfs)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo", "Bar", "Baz", "Qux");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(options -> options.minimumStringSwitchSize = 4)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::verifyRewrittenToIfs)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo", "Bar", "Baz", "Qux");
+ }
+
+ private void verifyRewrittenToIfs(CodeInspector inspector) {
+ MethodSubject testMethodSubject = inspector.clazz(Main.class).uniqueMethodWithName("test");
+ assertThat(testMethodSubject, isPresent());
+ assertTrue(testMethodSubject.streamInstructions().noneMatch(InstructionSubject::isSwitch));
+ assertEquals(
+ 3, testMethodSubject.streamInstructions().filter(InstructionSubject::isIf).count());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ test("Foo");
+ test("Bar");
+ test("Baz");
+ test("Qux");
+ }
+
+ @NeverInline
+ static void test(String str) {
+ int hashCode = str.hashCode();
+ int id = 0;
+ outer:
+ {
+ int nonZeroId;
+ switch (hashCode) {
+ case 70822: // "Foo".hashCode()
+ if (str.equals("Foo")) {
+ nonZeroId = 1;
+ break;
+ }
+ break outer;
+ case 66547: // "Bar".hashCode()
+ if (str.equals("Bar")) {
+ nonZeroId = 2;
+ break;
+ }
+ break outer;
+ case 66555: // "Baz".hashCode()
+ if (str.equals("Baz")) {
+ nonZeroId = 3;
+ break;
+ }
+ break outer;
+ default:
+ break outer;
+ }
+ id = nonZeroId;
+ }
+ switch (id) {
+ case 1:
+ System.out.println("Foo");
+ break;
+ case 2:
+ System.out.println("Bar");
+ break;
+ case 3:
+ System.out.println("Baz");
+ break;
+ default:
+ System.out.println("Qux");
+ break;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
index 3cbaed0..f07d6b6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/NestedInterfaceMethodTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -51,6 +52,7 @@
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableNoVerticalClassMergingAnnotations()
.addOptionsModification(
options -> {
@@ -112,5 +114,6 @@
@NeverClassInline
static class C extends A {}
+ @NoHorizontalClassMerging
static class Uninstantiated {}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
index 9d11655..12bc414 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceRemovalTest.java
@@ -85,6 +85,7 @@
void foo();
}
+ @NoHorizontalClassMerging
@NoVerticalClassMerging
interface J {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
index 768fbd6..6bc0862 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedinterfaces/UnusedInterfaceWithDefaultMethodTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -42,6 +43,7 @@
.addKeepMainRule(TestClass.class)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
@@ -86,6 +88,7 @@
void m();
}
+ @NoHorizontalClassMerging
@NoVerticalClassMerging
interface J extends I {
diff --git a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
index 548ce4d..049974c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/ProcessKotlinReflectionLibTest.java
@@ -100,12 +100,20 @@
@Test
public void testDontOptimize() throws Exception {
- test(TestShrinkerBuilder::noOptimization);
+ test(
+ builder -> {
+ builder.allowDiagnosticInfoMessages();
+ builder.noOptimization();
+ });
}
@Test
public void testDontObfuscate() throws Exception {
- test(TestShrinkerBuilder::noMinification);
+ test(
+ builder -> {
+ builder.allowDiagnosticInfoMessages();
+ builder.noMinification();
+ });
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 6406593..ca441cd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
@@ -58,10 +59,12 @@
.addKeepMainRule(mainClassName)
.addKeepKotlinMetadata()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
- .allowDiagnosticWarningMessages()
+ .allowDiagnosticMessages()
.setMinApi(parameters.getApiLevel())
.allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
.compile()
+ .assertNoErrorMessages()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
.run(parameters.getRuntime(), mainClassName);
CodeInspector inspector = result.inspector();
diff --git a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
index 9c01d63..8a27b3f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
@@ -11,8 +11,12 @@
import static org.junit.Assert.assertNotEquals;
import com.android.tools.r8.KotlinCompilerTool;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -60,10 +64,18 @@
options.enableEnumValueOptimization = enableSwitchMapRemoval;
options.enableEnumSwitchMapRemoval = enableSwitchMapRemoval;
})
- .allowDiagnosticWarningMessages()
+ .applyIf(
+ kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2),
+ R8TestBuilder::allowDiagnosticMessages,
+ R8TestBuilder::allowDiagnosticWarningMessages)
.setMinApi(parameters.getApiLevel())
.noMinification()
.compile()
+ .assertNoErrorMessages()
+ .applyIf(
+ kotlinParameters.getCompiler().is(KotlinCompilerVersion.KOTLINC_1_5_0_M2),
+ TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation,
+ TestCompileResult::assertNoInfoMessages)
.assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
.inspect(
inspector -> {
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index 4e376ed..6ee2517 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.KotlinTestParameters;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase;
@@ -91,11 +92,13 @@
.setMinApi(parameters.getApiLevel())
.addKeepAllClassesRule()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
- .allowDiagnosticWarningMessages()
+ .allowDiagnosticMessages()
.allowUnusedDontWarnKotlinReflectJvmInternal(kotlinc.isNot(KOTLINC_1_3_72))
.compile()
- .writeToZip(foo.toPath())
+ .assertNoErrorMessages()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
+ .writeToZip(foo.toPath())
.run(parameters.getRuntime(), PKG + ".SimpleReflectKt")
.assertSuccessWithOutputLines(EXPECTED_OUTPUT);
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index a01f7dc..0005549 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -812,7 +812,7 @@
Timing timing = Timing.empty();
InternalOptions options =
new InternalOptions(new DexItemFactory(), new Reporter(diagnosticsHandler));
- options.minApiLevel = minApi;
+ options.minApiLevel = AndroidApiLevel.getAndroidApiLevel(minApi);
options.intermediate = intermediate;
DexItemFactory factory = options.itemFactory;
AppView<?> appView = AppView.createForR8(DexApplication.builder(options, timing).build());
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
index 85c8459..3897351 100644
--- a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -118,10 +118,6 @@
for (DataEntryResource dataResource : getOriginalDataResources()) {
ImmutableList<String> object =
dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper));
- if (object == null) {
- object =
- dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper));
- }
assertNotNull("Resource not renamed as expected: " + dataResource.getName(), object);
}
});
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index dc3acee..0aa7a87 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -330,7 +330,6 @@
internalOptions.testing.disableMappingToOriginalProgramVerification = true)
.compile();
- compileResult.assertNoInfoMessages();
compileResult.assertNoErrorMessages();
CodeInspector inspector = compileResult.inspector();
@@ -415,28 +414,6 @@
}
@Test
- public void classSignatureOuter_valid() throws Exception {
- // class Outer<T extends Simple> extends Base<T>
- String signature = "<T:LSimple;>LBase<TT;>;";
- testSingleClass(
- "Outer",
- signature,
- TestDiagnosticMessages::assertNoWarnings,
- inspector -> {
- ClassSubject outer = inspector.clazz("Outer");
- ClassSubject simple = inspector.clazz("Simple");
- ClassSubject base = inspector.clazz("Base");
- String baseDescriptorWithoutSemicolon =
- base.getFinalDescriptor().substring(0, base.getFinalDescriptor().length() - 1);
- String minifiedSignature =
- "<T:" + simple.getFinalDescriptor() + ">" + baseDescriptorWithoutSemicolon + "<TT;>;";
- assertEquals(minifiedSignature, outer.getFinalSignatureAttribute());
- assertEquals(signature, outer.getOriginalSignatureAttribute());
- },
- false);
- }
-
- @Test
public void classSignatureExtendsInner_valid() throws Exception {
String signature = "LOuter<TT;>.Inner;";
testSingleClass(
@@ -469,9 +446,9 @@
inspector -> {
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer");
- assertEquals(signature, outer.getOriginalSignatureAttribute());
+ assertNull(outer.getOriginalSignatureAttribute());
},
- false);
+ true);
}
@Test
@@ -484,8 +461,8 @@
inspector -> {
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
- // TODO(b/1867459990): What to do here.
- assertEquals("LOuter$NotFound;", outer.getOriginalSignatureAttribute());
+ // TODO(b/186745999): What to do here.
+ assertNull(outer.getOriginalSignatureAttribute());
},
false);
}
@@ -500,7 +477,7 @@
inspector -> {
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
- assertEquals(signature, outer.getOriginalSignatureAttribute());
+ assertNull(outer.getOriginalSignatureAttribute());
},
false);
}
@@ -516,13 +493,13 @@
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
// TODO(b/1867459990): What to do here.
- assertEquals("LOuter$Inner$NotFound;", outer.getOriginalSignatureAttribute());
+ assertNull(outer.getOriginalSignatureAttribute());
},
false);
}
@Test
- public void classSignatureExtendsInner_multipleMestedInnerClassesNotFound() throws Exception {
+ public void classSignatureExtendsInner_multipleNestedInnerClassesNotFound() throws Exception {
String signature = "LOuter<TT;>.NotFound.AlsoNotFound;";
testSingleClass(
"Outer$ExtendsInner",
@@ -531,8 +508,7 @@
inspector -> {
assertThat(inspector.clazz("NotFound"), not(isPresent()));
ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
- // TODO(b/1867459990): What to do here.
- assertEquals("LOuter$NotFound$AlsoNotFound;", outer.getOriginalSignatureAttribute());
+ assertNull(outer.getOriginalSignatureAttribute());
},
false);
}
diff --git a/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java b/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
index 06c0ca4..bb1fed9 100644
--- a/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
@@ -15,15 +15,16 @@
@Test
public void buildWithD8AndRunWithDalvikOrArt() throws Exception {
- AndroidApp androidApp = ToolHelper.runR8(
- ToolHelper.prepareR8CommandBuilder(
- readClasses(ClassWithAnnotations.class, AnAnnotation.class))
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .addProguardConfiguration(
- ImmutableList.of("-keepattributes *Annotation*"), Origin.unknown())
- .build(),
- options -> options.minApiLevel = ToolHelper.getMinApiLevelForDexVm().getLevel());
+ AndroidApp androidApp =
+ ToolHelper.runR8(
+ ToolHelper.prepareR8CommandBuilder(
+ readClasses(ClassWithAnnotations.class, AnAnnotation.class))
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes *Annotation*"), Origin.unknown())
+ .build(),
+ options -> options.minApiLevel = ToolHelper.getMinApiLevelForDexVm());
String result = runOnArt(androidApp, ClassWithAnnotations.class);
Assert.assertEquals("@" + AnAnnotation.class.getCanonicalName() + "()", result);
}
diff --git a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
index f3ab264..03efc25 100644
--- a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
+++ b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
@@ -456,10 +456,10 @@
throws Exception {
AndroidApp app = readClasses(Path.class, Log.class, testClass);
if (compiler == Tool.D8) {
- app = compileWithD8(app, o -> o.minApiLevel = apiLevel.getLevel());
+ app = compileWithD8(app, o -> o.minApiLevel = apiLevel);
} else {
assert compiler == Tool.R8;
- app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
+ app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel);
}
checkPathParserMethods(app, testClass, a, b);
}
@@ -479,10 +479,10 @@
throws Exception {
AndroidApp app = readClasses(testClass);
if (compiler == Tool.D8) {
- app = compileWithD8(app, o -> o.minApiLevel = apiLevel.getLevel());
+ app = compileWithD8(app, o -> o.minApiLevel = apiLevel);
} else {
assert compiler == Tool.R8;
- app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
+ app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel);
}
CodeInspector inspector = new CodeInspector(app);
DexItemFactory factory = inspector.getFactory();
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index 550356e..9947888 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -5,6 +5,9 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticException;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.proguardConfigurationRuleDoesNotMatch;
+import static com.android.tools.r8.utils.codeinspector.Matchers.typeVariableNotInScope;
+import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -12,21 +15,26 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.R8;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
@@ -77,9 +85,10 @@
.addDontWarnGoogle()
.addDontWarnJavax()
.addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
+ .allowDiagnosticInfoMessages()
.compileWithExpectedDiagnostics(
- diagnostics ->
- diagnostics.assertErrorsMatch(diagnosticException(AssertionError.class)));
+ diagnostics -> diagnostics.assertErrorsMatch(diagnosticException(AssertionError.class)))
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation);
}
@Test
@@ -92,7 +101,9 @@
.addDontWarnGoogle()
.addDontWarnJavax()
.addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
- .compile();
+ .allowDiagnosticInfoMessages()
+ .compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation);
}
@Test
@@ -101,7 +112,11 @@
.addProgramFiles(R8_JAR)
.allowUnusedProguardConfigurationRules()
.addKeepRules("-keepclasseswithmembers class * { @" + ABSENT_ANNOTATION + " *; }")
- .compile()
+ .allowDiagnosticInfoMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertAllInfosMatch(
+ anyOf(typeVariableNotInScope(), proguardConfigurationRuleDoesNotMatch())))
.inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
}
@@ -113,7 +128,9 @@
.addDontWarnGoogle()
.addDontWarnJavax()
.addDontWarn("org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement")
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.inspect(
inspector -> {
ClassSubject clazz = inspector.clazz(CLASS_WITH_ANNOTATED_METHOD);
@@ -129,7 +146,9 @@
// TODO(b/159971974): Technically this rule does not hit anything and should fail due to
// missing allowUnusedProguardConfigurationRules()
.addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
}
@@ -139,7 +158,9 @@
.addProgramFiles(R8_JAR)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
.addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.inspect(
inspector -> {
assertEquals(1, inspector.allClasses().size());
@@ -162,7 +183,11 @@
.addProgramFiles(R8_JAR)
.allowUnusedProguardConfigurationRules()
.addKeepRules("-if class * -keep class <1> { @" + PRESENT_ANNOTATION + " *** *(...); }")
- .compile()
+ .allowDiagnosticInfoMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertAllInfosMatch(
+ anyOf(typeVariableNotInScope(), proguardConfigurationRuleDoesNotMatch())))
.inspect(inspector -> assertEquals(0, inspector.allClasses().size()));
}
@@ -172,7 +197,9 @@
.addProgramFiles(R8_JAR)
.addKeepClassRules(CLASS_WITH_ANNOTATED_METHOD)
.addKeepRules("-if class * -keep class <1> { @" + PRESENT_ANNOTATION + " *** *(...); }")
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.inspect(
inspector -> {
assertEquals(1, inspector.allClasses().size());
@@ -204,7 +231,10 @@
.addKeepRules("-keepclassmembers class * { @" + PRESENT_ANNOTATION + " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
+ .allowDiagnosticInfoMessages()
+ .apply(this::configureHorizontalClassMerging)
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.graphInspector();
GraphInspector ifThenKeepClassMembersInspector =
@@ -222,7 +252,10 @@
+ " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
+ .apply(this::configureHorizontalClassMerging)
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.graphInspector();
assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector);
@@ -241,7 +274,10 @@
+ " *** *(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
+ .apply(this::configureHorizontalClassMerging)
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.graphInspector();
assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector);
@@ -262,11 +298,29 @@
+ " *** <2>(...); }")
.addDontWarnGoogle()
.addDontWarnJavaxNullableAnnotation()
+ .apply(this::configureHorizontalClassMerging)
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.graphInspector();
assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
}
+ private void configureHorizontalClassMerging(R8FullTestBuilder testBuilder) {
+ // Attempt to ensure similar class merging across different builds by choosing the merge target
+ // as the class with the lexicographically smallest original name.
+ testBuilder.addOptionsModification(
+ options ->
+ options.testing.horizontalClassMergingTarget =
+ (appView, candidates, target) -> {
+ List<DexProgramClass> classes = Lists.newArrayList(candidates);
+ classes.sort(
+ Comparator.comparing(
+ clazz -> appView.graphLens().getOriginalType(clazz.getType())));
+ return ListUtils.first(classes);
+ });
+ }
+
private void assertRetainedClassesEqual(
GraphInspector referenceResult, GraphInspector conditionalResult) {
assertRetainedClassesEqual(referenceResult, conditionalResult, false, false);
diff --git a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
index 59c649b..7f6323a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ParameterTypeTest.java
@@ -11,10 +11,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.SingleTestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.jasmin.JasminBuilder;
@@ -33,16 +34,18 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
+@NoHorizontalClassMerging
interface B112452064SuperInterface1 {
void foo();
}
+@NoHorizontalClassMerging
interface B112452064SuperInterface2 {
void bar();
}
-interface B112452064SubInterface extends B112452064SuperInterface1, B112452064SuperInterface2 {
-}
+@NoHorizontalClassMerging
+interface B112452064SubInterface extends B112452064SuperInterface1, B112452064SuperInterface2 {}
class B112452064TestMain {
@@ -82,7 +85,8 @@
@Parameters(name = "{1}, argument removal: {0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
}
public ParameterTypeTest(boolean enableArgumentRemoval, TestParameters parameters) {
@@ -128,7 +132,8 @@
options.enableUnusedInterfaceRemoval = enableUnusedInterfaceRemoval;
options.enableVerticalClassMerging = enableVerticalClassMerging;
})
- .setMinApi(parameters.getRuntime())
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), B112452064TestMain.class)
.assertSuccessWithOutput(javaResult.stdout)
@@ -286,7 +291,6 @@
"return");
final String mainClassName = mainClass.name;
- String proguardConfig = keepMainProguardConfiguration(mainClassName, false, false);
// Run input program on java.
Path outputDirectory = temp.newFolder().toPath();
@@ -296,36 +300,34 @@
assertThat(javaResult.stdout, containsString(bar.name));
assertEquals(-1, javaResult.stderr.indexOf("ClassNotFoundException"));
- AndroidApp processedApp =
- compileWithR8(
- jasminBuilder.build(),
- proguardConfig,
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(jasminBuilder.buildClasses())
+ .addKeepMainRule(mainClassName)
+ .addOptionsModification(
options -> {
// Disable inlining to avoid the (short) tested method from being inlined and removed.
options.enableInlining = false;
options.enableArgumentRemoval = enableArgumentRemoval;
- });
-
- // Run processed (output) program on ART
- ProcessResult artResult = runOnArtRaw(processedApp, mainClassName);
- if (enableArgumentRemoval) {
- assertEquals(0, artResult.exitCode);
- } else {
- assertNotEquals(0, artResult.exitCode);
-
- DexVm.Version currentVersion = ToolHelper.getDexVm().getVersion();
- String errorMessage =
- currentVersion.isNewerThan(Version.V4_4_4)
- ? "type Precise Reference: Foo[] but expected Reference: SubInterface[]"
- : "[LFoo; is not instance of [LSubInterface;";
- assertThat(artResult.stderr, containsString(errorMessage));
- }
-
- assertEquals(-1, artResult.stderr.indexOf("ClassNotFoundException"));
-
- CodeInspector inspector = new CodeInspector(processedApp);
- ClassSubject subSubject = inspector.clazz(sub.name);
- assertNotEquals(enableArgumentRemoval, subSubject.isPresent());
+ })
+ .noMinification()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject subSubject = inspector.clazz(sub.name);
+ assertNotEquals(enableArgumentRemoval, subSubject.isPresent());
+ })
+ .run(parameters.getRuntime(), mainClassName)
+ .applyIf(
+ enableArgumentRemoval || parameters.isCfRuntime(),
+ SingleTestRunResult::assertSuccess,
+ result ->
+ result.assertFailureWithErrorThatMatches(
+ containsString(
+ parameters.getDexRuntimeVersion().isNewerThan(Version.V4_4_4)
+ ? "type Precise Reference: Foo[] but expected Reference: SubInterface[]"
+ : "[LFoo; is not instance of [LSubInterface;")))
+ .assertStderrMatches(not(containsString("ClassNotFoundException")));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
index c6e5eff..b427736 100644
--- a/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/clinit/ClassInitializationTriggersIndirectInterfaceInitializationTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
import com.android.tools.r8.NoUnusedInterfaceRemoval;
import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
@@ -43,6 +44,7 @@
.addKeepMainRule(Main.class)
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
.enableNoUnusedInterfaceRemovalAnnotations()
.enableNoVerticalClassMergingAnnotations()
.setMinApi(parameters.getApiLevel())
@@ -79,6 +81,7 @@
}
}
+ @NoHorizontalClassMerging
@NoUnusedInterfaceRemoval
@NoVerticalClassMerging
interface I {
@@ -94,6 +97,7 @@
}
}
+ @NoHorizontalClassMerging
@NoUnusedInterfaceRemoval
@NoVerticalClassMerging
interface J extends I {}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
index 94bdbf1..ac22ab7 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/WhyAreYouKeepingAllTest.java
@@ -48,7 +48,9 @@
.addKeepRuleFiles(MAIN_KEEP)
.addKeepRules(WHY_ARE_YOU_KEEPING_ALL)
.collectStdout()
+ .allowDiagnosticInfoMessages()
.compile()
+ .apply(TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.assertStdoutThatMatches(containsString("referenced in keep rule"))
// TODO(b/124655065): We should always know the reason for keeping.
// It is OK if this starts failing while the kept-graph API is incomplete, in which case
diff --git a/src/test/java/com/android/tools/r8/testing/ToolHelperTest.java b/src/test/java/com/android/tools/r8/testing/ToolHelperTest.java
index b0e1ab2..2353e6c 100644
--- a/src/test/java/com/android/tools/r8/testing/ToolHelperTest.java
+++ b/src/test/java/com/android/tools/r8/testing/ToolHelperTest.java
@@ -30,7 +30,8 @@
ToolHelper.getFirstSupportedAndroidJar(AndroidApiLevel.K_WATCH), AndroidApiLevel.L);
// All android.jar's for API level L are present.
for (AndroidApiLevel androidApiLevel : AndroidApiLevel.values()) {
- if (androidApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L)) {
+ if (androidApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L)
+ && androidApiLevel.isLessThanOrEqualTo(AndroidApiLevel.LATEST)) {
checkExpectedAndroidJar(
ToolHelper.getFirstSupportedAndroidJar(androidApiLevel), androidApiLevel);
}
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index eb269c1..d698e0b 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -108,7 +108,7 @@
SingleFileConsumer consumer = new SingleFileConsumer();
AndroidApp app = AndroidApp.builder().addDexProgramData(data, Origin.unknown()).build();
InternalOptions options = new InternalOptions();
- options.minApiLevel = apiLevel;
+ options.minApiLevel = AndroidApiLevel.getAndroidApiLevel(apiLevel);
options.programConsumer = consumer;
ExecutorService executor = ThreadUtils.getExecutorService(1);
try {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index 0410bc0..8179e48 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -10,6 +10,7 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ThrowableConsumer;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
@@ -21,6 +22,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -32,6 +34,8 @@
private final DexItemFactory dexItemFactory;
private final HorizontallyMergedClasses horizontallyMergedClasses;
+ private final Set<ClassReference> seen = new HashSet<>();
+
public HorizontallyMergedClassesInspector(
DexItemFactory dexItemFactory, HorizontallyMergedClasses horizontallyMergedClasses) {
this.dexItemFactory = dexItemFactory;
@@ -65,6 +69,14 @@
return horizontallyMergedClasses.getTargets();
}
+ public HorizontallyMergedClassesInspector applyIf(
+ boolean condition, ThrowableConsumer<HorizontallyMergedClassesInspector> consumer) {
+ if (condition) {
+ consumer.acceptWithRuntimeException(this);
+ }
+ return this;
+ }
+
public HorizontallyMergedClassesInspector assertMergedInto(Class<?> from, Class<?> target) {
assertEquals(
horizontallyMergedClasses.getMergeTargetOrDefault(toDexType(from)), toDexType(target));
@@ -102,6 +114,7 @@
+ StringUtils.join(", ", unmerged, DexType::getTypeName),
0,
unmerged.size());
+ seen.addAll(types.stream().map(DexType::asClassReference).collect(Collectors.toList()));
return this;
}
@@ -117,6 +130,17 @@
return this;
}
+ public HorizontallyMergedClassesInspector assertNoOtherClassesMerged() {
+ horizontallyMergedClasses.forEachMergeGroup(
+ (sources, target) -> {
+ for (DexType source : sources) {
+ assertTrue(source.getTypeName(), seen.contains(source.asClassReference()));
+ }
+ assertTrue(target.getTypeName(), seen.contains(target.asClassReference()));
+ });
+ return this;
+ }
+
public HorizontallyMergedClassesInspector assertClassesNotMerged(Class<?>... classes) {
return assertClassesNotMerged(Arrays.asList(classes));
}
@@ -145,6 +169,7 @@
assertTrue(type.isClassType());
assertFalse(horizontallyMergedClasses.hasBeenMergedOrIsMergeTarget(type));
}
+ seen.addAll(types.stream().map(DexType::asClassReference).collect(Collectors.toList()));
return this;
}
@@ -204,6 +229,7 @@
classReferences.size() - 1,
sources.size());
assertTrue(types.containsAll(sources));
+ seen.addAll(classReferences);
return this;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 3e70473..4f4b656 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -4,10 +4,13 @@
package com.android.tools.r8.utils.codeinspector;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.Collectors;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsMatcher;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessFlags;
import com.android.tools.r8.graph.DexClass;
@@ -220,6 +223,15 @@
};
}
+ public static Matcher<Diagnostic> typeVariableNotInScope() {
+ return DiagnosticsMatcher.diagnosticMessage(containsString("A type variable is not in scope"));
+ }
+
+ public static Matcher<Diagnostic> proguardConfigurationRuleDoesNotMatch() {
+ return DiagnosticsMatcher.diagnosticMessage(
+ containsString("Proguard configuration rule does not match anything"));
+ }
+
public static Matcher<Subject> isSynthetic() {
return new TypeSafeMatcher<Subject>() {
@Override
diff --git a/src/test/java/com/android/tools/r8/workaround/ExtendingInterfaceArrayCheckCastTest.java b/src/test/java/com/android/tools/r8/workaround/ExtendingInterfaceArrayCheckCastTest.java
new file mode 100644
index 0000000..eee79f0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/workaround/ExtendingInterfaceArrayCheckCastTest.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2021, 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.workaround;
+
+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 ExtendingInterfaceArrayCheckCastTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"Hello World!"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ExtendingInterfaceArrayCheckCastTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Utility {
+
+ public static <T extends Base<T> & I> void get(T enumType) {
+ // If using I[] as the local variable type, the compiler will automatically insert a checkcast
+ // and therefore circumvent b/188112948. Another option is to explicitly insert a checkcast.
+ I[] params = enumType.getParams();
+ tryGet(params);
+ }
+
+ // this will become void tryGet(I[] value) when compiled due to the extends.
+ public static <T extends I> void tryGet(T[] value) {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public interface I {
+ void call();
+ }
+
+ public static class Base<T extends Base<T>> {
+
+ T[] getParams() {
+ return null;
+ }
+ }
+
+ public static class Foo extends Base<Foo> implements I {
+
+ @Override
+ public void call() {
+ System.out.println("I::Foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Utility.get(new Foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/workaround/ExtendingInterfaceArrayMissingCheckCastTest.java b/src/test/java/com/android/tools/r8/workaround/ExtendingInterfaceArrayMissingCheckCastTest.java
new file mode 100644
index 0000000..971fac1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/workaround/ExtendingInterfaceArrayMissingCheckCastTest.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2021, 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.workaround;
+
+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)
+// This is a reproduction of b/188112948.
+public class ExtendingInterfaceArrayMissingCheckCastTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"Hello World!"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ExtendingInterfaceArrayMissingCheckCastTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.isDexRuntime(),
+ // TODO(b/188112948): This should not throw a verification error.
+ result -> result.assertFailureWithErrorThatThrows(VerifyError.class),
+ result -> result.assertSuccessWithOutputLines(EXPECTED));
+ }
+
+ public static class Utility {
+
+ public static <T extends Base<T> & I> void get(T enumType) {
+ // JDK 8 will add a checkcast [I such that the [Base array is cast to the correct type.
+ // JDK 9 and later will not add the checkcast.
+ tryGet(enumType.getParams());
+ }
+
+ // this will become void tryGet(I[] value) when compiled due to the extends.
+ public static <T extends I> void tryGet(T[] value) {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public interface I {
+ void call();
+ }
+
+ public static class Base<T extends Base<T>> {
+
+ T[] getParams() {
+ return null;
+ }
+ }
+
+ public static class Foo extends Base<Foo> implements I {
+
+ @Override
+ public void call() {
+ System.out.println("I::Foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Utility.get(new Foo());
+ }
+ }
+}