Merge commit '8f6c03384ef834acec9a9497024748b9815bb61f' into dev-release
diff --git a/build.gradle b/build.gradle
index 338df88..6050eda 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,7 +35,7 @@
ext {
androidSupportVersion = '25.4.0'
- asmVersion = '7.2'
+ asmVersion = '8.0'
espressoVersion = '3.0.0'
fastutilVersion = '7.2.0'
guavaVersion = '23.0'
@@ -279,6 +279,7 @@
examplesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
errorprone("com.google.errorprone:error_prone_core:$errorproneVersion")
+ testImplementation "org.jetbrains.kotlin:kotlin-reflect:1.3.31"
}
def r8LibPath = "$buildDir/libs/r8lib.jar"
@@ -404,6 +405,7 @@
"benchmarks/kotlin-benches",
"chrome/chrome_180917_ffbaa8",
"chrome/chrome_200430",
+ "chrome/monochrome_public_minimal_apks/chrome_200520",
"classlib",
"cf_segments",
"desugar/desugar_20180308",
@@ -2398,4 +2400,4 @@
println commandLine.join(' ')
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 5781482..cf475fa 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -393,7 +393,6 @@
assert internal.neverMergePrefixes.contains("j$.");
// Assert some of R8 optimizations are disabled.
- assert !internal.enableDynamicTypeOptimization;
assert !internal.enableInlining;
assert !internal.enableClassInlining;
assert !internal.enableHorizontalClassMerging;
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 3c7fdd2..ac56a10 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -130,7 +130,10 @@
+ ".",
" --intermediate # Compile an intermediate result intended for later",
" # merging.",
- " --file-per-class # Produce a separate dex file per input class",
+ " --file-per-class # Produce a separate dex file per class.",
+ " # Synthetic classes are in their own file.",
+ " --file-per-class-file # Produce a separate dex file per input .class file.",
+ " # Synthetic classes are with their originating class.",
" --no-desugaring # Force disable desugaring.",
" --desugared-lib <file> # Specify desugared library configuration.",
" # <file> is a desugared library configuration (json).",
@@ -210,6 +213,8 @@
compilationMode = CompilationMode.RELEASE;
} else if (arg.equals("--file-per-class")) {
outputMode = OutputMode.DexFilePerClass;
+ } else if (arg.equals("--file-per-class-file")) {
+ outputMode = OutputMode.DexFilePerClassFile;
} else if (arg.equals("--classfile")) {
outputMode = OutputMode.ClassFile;
} else if (arg.equals("--output")) {
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index b1072df..9b0f9de 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -162,7 +162,6 @@
assert !internal.passthroughDexCode;
// Assert some of R8 optimizations are disabled.
- assert !internal.enableDynamicTypeOptimization;
assert !internal.enableInlining;
assert !internal.enableClassInlining;
assert !internal.enableHorizontalClassMerging;
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java
index 0802127..e083cec 100644
--- a/src/main/java/com/android/tools/r8/L8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -18,8 +18,8 @@
public class L8CommandParser extends BaseCompilerCommandParser<L8Command, L8Command.Builder> {
- private static final Set<String> OPTIONS_WITH_PARAMETER =
- ImmutableSet.of("--output", "--lib", MIN_API_FLAG, "--desugared-lib", THREAD_COUNT_FLAG);
+ private static final Set<String> OPTIONS_WITH_PARAMETER = ImmutableSet.of(
+ "--output", "--lib", MIN_API_FLAG, "--desugared-lib", THREAD_COUNT_FLAG, "--pg-conf");
public static void main(String[] args) throws CompilationFailedException {
L8Command command = parse(args, Origin.root()).build();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8406706..34677e0 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -515,10 +515,8 @@
assert changed;
appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
application = application.asDirect().rewrittenWithLens(lens);
- lens.initializeCacheForLookupMethodInAllContexts();
appViewWithLiveness.setAppInfo(
appViewWithLiveness.appInfo().rewrittenWithLens(application.asDirect(), lens));
- lens.unsetCacheForLookupMethodInAllContexts();
}
timing.end();
}
@@ -544,6 +542,7 @@
timing.begin("UninstantiatedTypeOptimization");
UninstantiatedTypeOptimizationGraphLense lens =
new UninstantiatedTypeOptimization(appViewWithLiveness)
+ .strenghtenOptimizationInfo()
.run(
new MethodPoolCollection(appViewWithLiveness, subtypingInfo),
executorService,
@@ -697,7 +696,7 @@
appView.withGeneratedMessageLiteBuilderShrinker(
shrinker ->
- shrinker.removeDeadBuilderReferencesFromDynamicMethods(
+ shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
appViewWithLiveness, executorService, timing));
if (options.isShrinking()) {
@@ -914,6 +913,10 @@
shrinker ->
shrinker.setDeadProtoTypes(appViewWithLiveness.appInfo().getDeadProtoTypes()));
}
+ appView.withGeneratedMessageLiteBuilderShrinker(
+ shrinker ->
+ shrinker.rewriteDeadBuilderReferencesFromDynamicMethods(
+ appViewWithLiveness, executorService, timing));
return appViewWithLiveness;
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 656f3aa..5abbc25 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -842,7 +842,6 @@
? LineNumberOptimization.ON
: LineNumberOptimization.OFF;
- assert internal.enableDynamicTypeOptimization || !proguardConfiguration.isOptimizing();
assert internal.enableHorizontalClassMerging || !proguardConfiguration.isOptimizing();
assert !internal.enableTreeShakingOfLibraryMethodOverrides;
assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
diff --git a/src/main/java/com/android/tools/r8/R8CommandParser.java b/src/main/java/com/android/tools/r8/R8CommandParser.java
index 6220eb4..8c93579 100644
--- a/src/main/java/com/android/tools/r8/R8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/R8CommandParser.java
@@ -33,6 +33,7 @@
"--pg-conf",
"--pg-map-output",
"--desugared-lib",
+ "--desugared-lib-pg-conf-output",
THREAD_COUNT_FLAG);
private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of("--feature");
@@ -83,6 +84,8 @@
+ " <file>.",
" --desugared-lib <file> # Specify desugared library configuration.",
" # <file> is a desugared library configuration (json).",
+ " --desugared-lib-pg-conf-output <file> # Output the Proguard configuration ",
+ " # needed by L8 to <file>.",
" --no-tree-shaking # Force disable tree shaking of unreachable classes.",
" --no-minification # Force disable minification of names.",
" --no-data-resources # Ignore all data resources.",
@@ -252,6 +255,9 @@
builder.setProguardMapOutputPath(Paths.get(nextArg));
} else if (arg.equals("--desugared-lib")) {
builder.addDesugaredLibraryConfiguration(StringResource.fromFile(Paths.get(nextArg)));
+ } else if (arg.equals("--desugared-lib-pg-conf-output")) {
+ StringConsumer consumer = new StringConsumer.FileConsumer(Paths.get(nextArg));
+ builder.setDesugaredLibraryKeepRuleConsumer(consumer);
} else if (arg.equals("--no-data-resources")) {
state.includeDataResources = false;
} else if (arg.startsWith("--")) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 8ee86f3..5d102bdf 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -455,7 +455,7 @@
private void insertAttributeAnnotations() {
// Convert inner-class attributes to DEX annotations
for (DexProgramClass clazz : application.classes()) {
- EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethod();
+ EnclosingMethodAttribute enclosingMethod = clazz.getEnclosingMethodAttribute();
List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
if (enclosingMethod == null && innerClasses.isEmpty()) {
continue;
@@ -521,7 +521,7 @@
}
// Clear the attribute structures now that they are represented in annotations.
- clazz.clearEnclosingMethod();
+ clazz.clearEnclosingMethodAttribute();
clazz.clearInnerClasses();
}
}
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 26dca03..a416e51 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -642,8 +642,7 @@
int size,
DexMethodAnnotation[] annotations,
DexParameterAnnotation[] parameters,
- boolean skipCodes,
- boolean ensureNonAbstract) {
+ boolean skipCodes) {
DexEncodedMethod[] methods = new DexEncodedMethod[size];
int methodIndex = 0;
MemberAnnotationIterator<DexMethod, DexAnnotationSet> annotationIterator =
@@ -661,19 +660,13 @@
code = codes.get(codeOff);
}
DexMethod method = indexedItems.getMethod(methodIndex);
- DexEncodedMethod encodedMethod =
+ methods[i] =
new DexEncodedMethod(
method,
accessFlags,
annotationIterator.getNextFor(method),
parameterAnnotationsIterator.getNextFor(method),
code);
- if (accessFlags.isAbstract() && ensureNonAbstract) {
- accessFlags.unsetAbstract();
- assert !options.isGeneratingClassFiles();
- encodedMethod = encodedMethod.toEmptyThrowingMethodDex(false);
- }
- methods[i] = encodedMethod;
}
return methods;
}
@@ -754,17 +747,13 @@
directMethodsSize,
annotationsDirectory.methods,
annotationsDirectory.parameters,
- classKind != ClassKind.PROGRAM,
- options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
- && !flags.isAbstract());
+ classKind != ClassKind.PROGRAM);
virtualMethods =
readMethods(
virtualMethodsSize,
annotationsDirectory.methods,
annotationsDirectory.parameters,
- classKind != ClassKind.PROGRAM,
- options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
- && !flags.isAbstract());
+ classKind != ClassKind.PROGRAM);
}
AttributesAndAnnotations attrs =
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 0b5d100..3706dec 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -21,9 +21,6 @@
private final DexApplication app;
private final DexItemFactory dexItemFactory;
- // TODO(b/151804585): Remove this cache.
- private final ConcurrentHashMap<DexType, Map<DexField, DexEncodedField>> fieldDefinitionsCache;
-
// For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
// class being optimized.
private final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses;
@@ -33,31 +30,28 @@
private final BooleanBox obsolete;
public AppInfo(DexApplication application) {
- this(application, new ConcurrentHashMap<>(), new ConcurrentHashMap<>(), new BooleanBox());
+ this(application, new ConcurrentHashMap<>(), new BooleanBox());
}
// For desugaring.
protected AppInfo(AppInfo appInfo) {
- this(appInfo.app, appInfo.fieldDefinitionsCache, appInfo.synthesizedClasses, appInfo.obsolete);
+ this(appInfo.app, appInfo.synthesizedClasses, appInfo.obsolete);
}
// For AppInfoWithLiveness.
protected AppInfo(AppInfoWithClassHierarchy previous) {
this(
((AppInfo) previous).app,
- new ConcurrentHashMap<>(((AppInfo) previous).fieldDefinitionsCache),
new ConcurrentHashMap<>(((AppInfo) previous).synthesizedClasses),
new BooleanBox());
}
private AppInfo(
DexApplication application,
- ConcurrentHashMap<DexType, Map<DexField, DexEncodedField>> fieldDefinitionsCache,
ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses,
BooleanBox obsolete) {
this.app = application;
this.dexItemFactory = application.dexItemFactory;
- this.fieldDefinitionsCache = fieldDefinitionsCache;
this.synthesizedClasses = synthesizedClasses;
this.obsolete = obsolete;
}
@@ -102,7 +96,6 @@
assert checkIfObsolete();
assert clazz.type.isD8R8SynthesizedClassType();
DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
- invalidateFieldCacheFor(clazz.type);
assert previous == null || previous == clazz;
}
@@ -131,19 +124,6 @@
}
@Override
- public DexDefinition definitionFor(DexReference reference) {
- assert checkIfObsolete();
- if (reference.isDexType()) {
- return definitionFor(reference.asDexType());
- }
- if (reference.isDexMethod()) {
- return definitionFor(reference.asDexMethod());
- }
- assert reference.isDexField();
- return definitionFor(reference.asDexField());
- }
-
- @Override
public DexClass definitionFor(DexType type) {
return definitionForWithoutExistenceAssert(type);
}
@@ -181,6 +161,7 @@
return definition == null ? Origin.unknown() : definition.origin;
}
+ @Deprecated
@Override
public DexEncodedMethod definitionFor(DexMethod method) {
assert checkIfObsolete();
@@ -195,20 +176,6 @@
return clazz.getMethodCollection().getMethod(method);
}
- @Override
- public DexEncodedField definitionFor(DexField field) {
- assert checkIfObsolete();
- return getFieldDefinitions(field.holder).get(field);
- }
-
- private Map<DexField, DexEncodedField> getFieldDefinitions(DexType type) {
- return fieldDefinitionsCache.computeIfAbsent(type, this::computeFieldDefinitions);
- }
-
- public void invalidateFieldCacheFor(DexType type) {
- fieldDefinitionsCache.remove(type);
- }
-
/**
* Lookup static method on the method holder, or answers null.
*
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 8077269..b4534f5 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -226,16 +226,7 @@
return this.sourceDebugExtensions.get(clazz);
}
- @Override
- public final DexDefinition definitionFor(DexReference reference) {
- return appInfo().definitionFor(reference);
- }
-
- @Override
- public final DexEncodedField definitionFor(DexField field) {
- return appInfo().definitionFor(field);
- }
-
+ @Deprecated
@Override
public final DexEncodedMethod definitionFor(DexMethod method) {
return appInfo().definitionFor(method);
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 030497d..e33792c 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -167,6 +167,10 @@
return isSet(Constants.ACC_ABSTRACT);
}
+ public void demoteFromAbstract() {
+ demote(Constants.ACC_ABSTRACT);
+ }
+
public void setAbstract() {
set(Constants.ACC_ABSTRACT);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index c2cdc32..09aeb11 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -176,12 +176,10 @@
private boolean verifyNoAbstractMethodsOnNonAbstractClasses(
Iterable<DexEncodedMethod> methods, InternalOptions options) {
- if (options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) {
- if (!isAbstract()) {
- for (DexEncodedMethod method : methods) {
- assert !method.isAbstract()
- : "Non-abstract method on abstract class: `" + method.method.toSourceString() + "`";
- }
+ if (options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() && !isAbstract()) {
+ for (DexEncodedMethod method : methods) {
+ assert !method.isAbstract()
+ : "Non-abstract method on abstract class: `" + method.method.toSourceString() + "`";
}
}
return true;
@@ -406,6 +404,15 @@
return methodCollection.getVirtualMethod(predicate);
}
+ /** Find member in this class matching {@param member}. */
+ @SuppressWarnings("unchecked")
+ public <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> D lookupMember(
+ DexMember<D, R> member) {
+ DexEncodedMember<?, ?> definition =
+ member.isDexField() ? lookupField(member.asDexField()) : lookupMethod(member.asDexMethod());
+ return (D) definition;
+ }
+
/** Find method in this class matching {@param method}. */
public DexEncodedMethod lookupMethod(DexMethod method) {
return methodCollection.getMethod(method);
@@ -700,15 +707,15 @@
return innerClasses;
}
- public EnclosingMethodAttribute getEnclosingMethod() {
+ public EnclosingMethodAttribute getEnclosingMethodAttribute() {
return enclosingMethod;
}
- public void clearEnclosingMethod() {
+ public void clearEnclosingMethodAttribute() {
enclosingMethod = null;
}
- public void removeEnclosingMethod(Predicate<EnclosingMethodAttribute> predicate) {
+ public void removeEnclosingMethodAttribute(Predicate<EnclosingMethodAttribute> predicate) {
if (enclosingMethod != null && predicate.test(enclosingMethod)) {
enclosingMethod = null;
}
@@ -752,7 +759,7 @@
public boolean isMemberClass() {
InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
boolean isMember = innerClass != null && innerClass.getOuter() != null && innerClass.isNamed();
- assert !isMember || getEnclosingMethod() == null;
+ assert !isMember || getEnclosingMethodAttribute() == null;
return isMember;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index 3d4e92b..214e41c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -6,18 +6,9 @@
public interface DexDefinitionSupplier {
- DexDefinition definitionFor(DexReference reference);
-
- DexEncodedField definitionFor(DexField field);
-
+ @Deprecated
DexEncodedMethod definitionFor(DexMethod method);
- @SuppressWarnings("unchecked")
- default <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
- DexEncodedMember<D, R> definitionFor(DexMember<D, R> member) {
- return (DexEncodedMember<D, R>) definitionFor((DexReference) member);
- }
-
DexClass definitionFor(DexType type);
DexProgramClass definitionForProgramType(DexType type);
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 7071fda..c12f3f4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -705,6 +705,10 @@
return builder.toString();
}
+ public void clearParameterAnnotations() {
+ parameterAnnotationsList = ParameterAnnotationsList.empty();
+ }
+
public String toSmaliString(ClassNameMapper naming) {
checkIfObsolete();
StringBuilder builder = new StringBuilder();
@@ -767,17 +771,7 @@
null);
}
- public DexCode buildEmptyThrowingDexCode() {
- Instruction insn[] = {new Const(0, 0), new Throw(0)};
- return generateCodeFromTemplate(1, 0, insn);
- }
-
public DexEncodedMethod toEmptyThrowingMethod(InternalOptions options) {
- // Note that we are not marking this instance obsolete, since this util is only used by
- // TreePruner while keeping non-live yet targeted, empty method. Such method can be retrieved
- // again only during the 2nd round of tree sharking, and seeing an obsolete empty body v.s.
- // seeing this empty throwing code do not matter.
- // If things are changed, the cure point is obsolete instances inside RootSet.
return options.isGeneratingClassFiles()
? toEmptyThrowingMethodCf()
: toEmptyThrowingMethodDex(true);
@@ -791,7 +785,28 @@
if (setIsLibraryOverride && isNonPrivateVirtualMethod()) {
builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
}
- return builder.build();
+ DexEncodedMethod result = builder.build();
+ setObsolete();
+ return result;
+ }
+
+ private DexEncodedMethod toEmptyThrowingMethodCf() {
+ checkIfObsolete();
+ assert !shouldNotHaveCode();
+ Builder builder = builder(this);
+ builder.setCode(buildEmptyThrowingCfCode());
+ if (isNonPrivateVirtualMethod()) {
+ builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
+ }
+ DexEncodedMethod result = builder.build();
+ setObsolete();
+ return result;
+ }
+
+ public Code buildEmptyThrowingCode(InternalOptions options) {
+ return options.isGeneratingClassFiles()
+ ? buildEmptyThrowingCfCode()
+ : buildEmptyThrowingDexCode();
}
public CfCode buildEmptyThrowingCfCode() {
@@ -805,17 +820,9 @@
Collections.emptyList());
}
- private DexEncodedMethod toEmptyThrowingMethodCf() {
- checkIfObsolete();
- assert !shouldNotHaveCode();
- Builder builder = builder(this);
- builder.setCode(buildEmptyThrowingCfCode());
- if (isNonPrivateVirtualMethod()) {
- builder.setIsLibraryMethodOverride(isLibraryMethodOverride());
- }
- // Note that we are not marking this instance obsolete:
- // refer to Dex-backend version of this method above.
- return builder.build();
+ public DexCode buildEmptyThrowingDexCode() {
+ Instruction insn[] = {new Const(0, 0), new Throw(0)};
+ return generateCodeFromTemplate(1, 0, insn);
}
public DexEncodedMethod toMethodThatLogsError(AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index d10a2c8..84d581e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -6,6 +6,9 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.naming.NamingLens;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
public class DexField extends DexMember<DexEncodedField, DexField> {
@@ -23,6 +26,36 @@
}
@Override
+ public DexEncodedField lookupOnClass(DexClass clazz) {
+ return clazz != null ? clazz.lookupField(this) : null;
+ }
+
+ @Override
+ public <T> T apply(
+ Function<DexType, T> classConsumer,
+ Function<DexField, T> fieldConsumer,
+ Function<DexMethod, T> methodConsumer) {
+ return fieldConsumer.apply(this);
+ }
+
+ @Override
+ public void accept(
+ Consumer<DexType> classConsumer,
+ Consumer<DexField> fieldConsumer,
+ Consumer<DexMethod> methodConsumer) {
+ fieldConsumer.accept(this);
+ }
+
+ @Override
+ public <T> void accept(
+ BiConsumer<DexType, T> classConsumer,
+ BiConsumer<DexField, T> fieldConsumer,
+ BiConsumer<DexMethod, T> methodConsumer,
+ T arg) {
+ fieldConsumer.accept(this, arg);
+ }
+
+ @Override
public int computeHashCode() {
return holder.hashCode()
+ type.hashCode() * 7
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 1a318c0..d61746d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -147,6 +147,10 @@
public final DexString boxedNumberDescriptor = createString("Ljava/lang/Number;");
public final DexString boxedVoidDescriptor = createString("Ljava/lang/Void;");
+ public final DexString waitMethodName = createString("wait");
+ public final DexString notifyMethodName = createString("notify");
+ public final DexString notifyAllMethodName = createString("notifyAll");
+
public final DexString unboxBooleanMethodName = createString("booleanValue");
public final DexString unboxByteMethodName = createString("byteValue");
public final DexString unboxCharMethodName = createString("charValue");
@@ -164,6 +168,7 @@
public final DexString startsWithMethodName = createString("startsWith");
public final DexString endsWithMethodName = createString("endsWith");
public final DexString equalsMethodName = createString("equals");
+ public final DexString hashCodeMethodName = createString("hashCode");
public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase");
public final DexString contentEqualsMethodName = createString("contentEquals");
public final DexString indexOfMethodName = createString("indexOf");
@@ -1269,6 +1274,7 @@
public final DexMethod toString;
public final DexMethod compareTo;
public final DexMethod equals;
+ public final DexMethod hashCode;
public final DexMethod constructor =
createMethod(enumType, createProto(voidType, stringType, intType), constructorMethodName);
@@ -1309,6 +1315,8 @@
equalsMethodName,
booleanDescriptor,
new DexString[] {objectDescriptor});
+ hashCode =
+ createMethod(enumDescriptor, hashCodeMethodName, intDescriptor, DexString.EMPTY_ARRAY);
}
public boolean isValuesMethod(DexMethod method, DexClass enumClass) {
@@ -1495,7 +1503,7 @@
createMethod(stringDescriptor, compareToIgnoreCaseMethodName, intDescriptor,
needsOneString);
- hashCode = createMethod(stringType, createProto(intType), "hashCode");
+ hashCode = createMethod(stringType, createProto(intType), hashCodeMethodName);
valueOf = createMethod(
stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
toString = createMethod(
diff --git a/src/main/java/com/android/tools/r8/graph/DexMember.java b/src/main/java/com/android/tools/r8/graph/DexMember.java
index d04808b..02fd431 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMember.java
@@ -13,6 +13,10 @@
this.holder = holder;
}
+ public DexEncodedMember<?, ?> lookupOnClass(DexClass clazz) {
+ return clazz != null ? clazz.lookupMember(this) : null;
+ }
+
public abstract boolean match(R entry);
public abstract boolean match(D entry);
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 d3dd5fc..8a50931 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -11,6 +11,9 @@
import com.android.tools.r8.references.TypeReference;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> {
@@ -29,6 +32,36 @@
}
@Override
+ public <T> T apply(
+ Function<DexType, T> classConsumer,
+ Function<DexField, T> fieldConsumer,
+ Function<DexMethod, T> methodConsumer) {
+ return methodConsumer.apply(this);
+ }
+
+ @Override
+ public void accept(
+ Consumer<DexType> classConsumer,
+ Consumer<DexField> fieldConsumer,
+ Consumer<DexMethod> methodConsumer) {
+ methodConsumer.accept(this);
+ }
+
+ @Override
+ public <T> void accept(
+ BiConsumer<DexType, T> classConsumer,
+ BiConsumer<DexField, T> fieldConsumer,
+ BiConsumer<DexMethod, T> methodConsumer,
+ T arg) {
+ methodConsumer.accept(this, arg);
+ }
+
+ @Override
+ public DexEncodedMethod lookupOnClass(DexClass clazz) {
+ return clazz != null ? clazz.lookupMember(this) : null;
+ }
+
+ @Override
public String toString() {
return "Method " + holder + "." + name + " " + proto.toString();
}
@@ -191,4 +224,8 @@
return name == dexItemFactory.deserializeLambdaMethodName
&& proto == dexItemFactory.deserializeLambdaMethodProto;
}
+
+ public boolean isInstanceInitializer(DexDefinitionSupplier definitions) {
+ return definitions.dexItemFactory().isConstructor(this);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 900712b..423acef 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -133,6 +133,10 @@
synthesizedDirectlyFrom.forEach(this::addSynthesizedFrom);
}
+ public void forEachProgramField(Consumer<ProgramField> consumer) {
+ forEachField(field -> consumer.accept(new ProgramField(this, field)));
+ }
+
public void forEachProgramMethod(Consumer<ProgramMethod> consumer) {
forEachProgramMethodMatching(alwaysTrue(), consumer);
}
@@ -242,8 +246,8 @@
if (interfaces != null) {
interfaces.collectIndexedItems(indexedItems, method, instructionOffset);
}
- if (getEnclosingMethod() != null) {
- getEnclosingMethod().collectIndexedItems(indexedItems);
+ if (getEnclosingMethodAttribute() != null) {
+ getEnclosingMethodAttribute().collectIndexedItems(indexedItems);
}
for (InnerClassAttribute attribute : getInnerClasses()) {
attribute.collectIndexedItems(indexedItems);
@@ -269,7 +273,7 @@
@Override
void collectMixedSectionItems(MixedSectionCollection mixedItems) {
- assert getEnclosingMethod() == null;
+ assert getEnclosingMethodAttribute() == null;
assert getInnerClasses().isEmpty();
if (hasAnnotations()) {
mixedItems.setAnnotationsDirectoryForClass(this, new DexAnnotationDirectory(this));
@@ -278,7 +282,7 @@
@Override
public void addDependencies(MixedSectionCollection collector) {
- assert getEnclosingMethod() == null;
+ assert getEnclosingMethodAttribute() == null;
assert getInnerClasses().isEmpty();
// We only have a class data item if there are methods or fields.
if (hasMethodsOrFields()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexReference.java b/src/main/java/com/android/tools/r8/graph/DexReference.java
index 42286db..c37c9de 100644
--- a/src/main/java/com/android/tools/r8/graph/DexReference.java
+++ b/src/main/java/com/android/tools/r8/graph/DexReference.java
@@ -3,7 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -13,6 +14,22 @@
*/
public abstract class DexReference extends IndexedDexItem {
+ public abstract <T> T apply(
+ Function<DexType, T> classConsumer,
+ Function<DexField, T> fieldConsumer,
+ Function<DexMethod, T> methodConsumer);
+
+ public abstract void accept(
+ Consumer<DexType> classConsumer,
+ Consumer<DexField> fieldConsumer,
+ Consumer<DexMethod> methodConsumer);
+
+ public abstract <T> void accept(
+ BiConsumer<DexType, T> classConsumer,
+ BiConsumer<DexField, T> fieldConsumer,
+ BiConsumer<DexMethod, T> methodConsumer,
+ T arg);
+
public boolean isDexType() {
return false;
}
@@ -49,22 +66,6 @@
return DexItem.filter(stream, DexReference.class);
}
- public DexDefinition toDefinition(AppInfo appInfo) {
- if (isDexType()) {
- return appInfo.definitionFor(asDexType());
- } else if (isDexField()) {
- return appInfo.definitionFor(asDexField());
- } else {
- assert isDexMethod();
- return appInfo.definitionFor(asDexMethod());
- }
- }
-
- public static Stream<DexDefinition> mapToDefinition(
- Stream<DexReference> references, AppInfo appInfo) {
- return references.map(r -> r.toDefinition(appInfo)).filter(Objects::nonNull);
- }
-
private static <T extends DexReference> Stream<T> filter(
Stream<DexReference> stream,
Predicate<DexReference> pred,
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 01c5298..fc7ae3e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -24,14 +24,15 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
-import com.android.tools.r8.utils.Pair;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
public class DexType extends DexReference implements PresortedComparable<DexType> {
@@ -117,6 +118,31 @@
}
@Override
+ public <T> T apply(
+ Function<DexType, T> classConsumer,
+ Function<DexField, T> fieldConsumer,
+ Function<DexMethod, T> methodConsumer) {
+ return classConsumer.apply(this);
+ }
+
+ @Override
+ public void accept(
+ Consumer<DexType> classConsumer,
+ Consumer<DexField> fieldConsumer,
+ Consumer<DexMethod> methodConsumer) {
+ classConsumer.accept(this);
+ }
+
+ @Override
+ public <T> void accept(
+ BiConsumer<DexType, T> classConsumer,
+ BiConsumer<DexField, T> fieldConsumer,
+ BiConsumer<DexMethod, T> methodConsumer,
+ T arg) {
+ classConsumer.accept(this, arg);
+ }
+
+ @Override
public String toSourceString() {
if (toStringCache == null) {
// TODO(ager): Pass in a ProguardMapReader to map names back to original names.
@@ -409,15 +435,4 @@
public String getPackageName() {
return DescriptorUtils.getPackageNameFromBinaryName(toBinaryName());
}
-
- public Pair<String, String> rewritingPrefixIn(Map<String, String> map) {
- // TODO(b/134732760): Rewrite this to use descriptors and not Strings.
- String javaClassName = this.toString();
- for (String rewritePrefix : map.keySet()) {
- if (javaClassName.startsWith(rewritePrefix)) {
- return new Pair<>(rewritePrefix, map.get(rewritePrefix));
- }
- }
- return null;
- }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 5f0a349..3699d2e 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -73,24 +73,7 @@
return classpathClasses;
}
- @Override
- public DexDefinition definitionFor(DexReference reference) {
- if (reference.isDexType()) {
- return definitionFor(reference.asDexType());
- }
- if (reference.isDexMethod()) {
- return definitionFor(reference.asDexMethod());
- }
- assert reference.isDexField();
- return definitionFor(reference.asDexField());
- }
-
- @Override
- public DexEncodedField definitionFor(DexField field) {
- DexClass clazz = definitionFor(field.holder);
- return clazz != null ? clazz.lookupField(field) : null;
- }
-
+ @Deprecated
@Override
public DexEncodedMethod definitionFor(DexMethod method) {
DexClass clazz = definitionFor(method.holder);
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index ea55ec4..0bcbc76 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.shaking.KeepInfoCollection;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
@@ -12,13 +14,11 @@
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
-import java.util.ArrayDeque;
-import java.util.Collections;
-import java.util.Deque;
+import java.util.ArrayList;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Function;
import java.util.function.Supplier;
/**
@@ -189,13 +189,6 @@
public abstract RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method);
- // Context sensitive graph lenses should override this method.
- public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
- DexMethod result = lookupMethod(method);
- assert result != null;
- return ImmutableSet.of(result);
- }
-
public abstract DexField lookupField(DexField field);
public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
@@ -263,6 +256,14 @@
return true;
}
+ public <T extends DexReference> boolean assertPinnedNotModified(KeepInfoCollection keepInfo) {
+ List<DexReference> pinnedItems = new ArrayList<>();
+ keepInfo.forEachPinnedType(pinnedItems::add);
+ keepInfo.forEachPinnedMethod(pinnedItems::add);
+ keepInfo.forEachPinnedField(pinnedItems::add);
+ return assertReferencesNotModified(pinnedItems);
+ }
+
public <T extends DexReference> boolean assertReferencesNotModified(Iterable<T> references) {
for (DexReference reference : references) {
if (reference.isDexField()) {
@@ -280,37 +281,40 @@
return true;
}
- public ImmutableSet<DexReference> rewriteReferencesConservatively(Set<DexReference> original) {
- ImmutableSet.Builder<DexReference> builder = ImmutableSet.builder();
- for (DexReference item : original) {
- if (item.isDexMethod()) {
- DexMethod method = item.asDexMethod();
- builder.addAll(lookupMethodInAllContexts(method));
- } else {
- builder.add(lookupReference(item));
- }
+ public DexReference rewriteReference(DexReference reference) {
+ if (reference.isDexField()) {
+ return getRenamedFieldSignature(reference.asDexField());
}
- return builder.build();
+ if (reference.isDexMethod()) {
+ return getRenamedMethodSignature(reference.asDexMethod());
+ }
+ assert reference.isDexType();
+ return lookupType(reference.asDexType());
}
- public Object2BooleanMap<DexReference> rewriteReferencesConservatively(
- Object2BooleanMap<DexReference> original) {
- Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
- for (Object2BooleanMap.Entry<DexReference> entry : original.object2BooleanEntrySet()) {
- DexReference item = entry.getKey();
- if (item.isDexMethod()) {
- DexMethod method = item.asDexMethod();
- for (DexMethod candidate : lookupMethodInAllContexts(method)) {
- result.put(candidate, entry.getBooleanValue());
- }
- } else {
- result.put(lookupReference(item), entry.getBooleanValue());
- }
+ public Set<DexReference> rewriteReferences(Set<DexReference> references) {
+ Set<DexReference> result = SetUtils.newIdentityHashSet(references.size());
+ for (DexReference reference : references) {
+ result.add(rewriteReference(reference));
}
return result;
}
- public ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(Set<DexMethod> methods) {
+ public <T> ImmutableMap<DexReference, T> rewriteReferenceKeys(Map<DexReference, T> map) {
+ ImmutableMap.Builder<DexReference, T> builder = ImmutableMap.builder();
+ map.forEach((reference, value) -> builder.put(rewriteReference(reference), value));
+ return builder.build();
+ }
+
+ public Object2BooleanMap<DexReference> rewriteReferenceKeys(Object2BooleanMap<DexReference> map) {
+ Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
+ for (Object2BooleanMap.Entry<DexReference> entry : map.object2BooleanEntrySet()) {
+ result.put(rewriteReference(entry.getKey()), entry.getBooleanValue());
+ }
+ return result;
+ }
+
+ public ImmutableSortedSet<DexMethod> rewriteMethods(Set<DexMethod> methods) {
ImmutableSortedSet.Builder<DexMethod> builder =
new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
for (DexMethod method : methods) {
@@ -319,25 +323,24 @@
return builder.build();
}
- public ImmutableSortedSet<DexMethod> rewriteMethodsConservatively(Set<DexMethod> original) {
- ImmutableSortedSet.Builder<DexMethod> builder =
+ public <T> ImmutableMap<DexField, T> rewriteFieldKeys(Map<DexField, T> map) {
+ ImmutableMap.Builder<DexField, T> builder = ImmutableMap.builder();
+ map.forEach((field, value) -> builder.put(getRenamedFieldSignature(field), value));
+ return builder.build();
+ }
+
+ public ImmutableSet<DexType> rewriteTypes(Set<DexType> types) {
+ ImmutableSortedSet.Builder<DexType> builder =
new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
- if (isContextFreeForMethods()) {
- for (DexMethod item : original) {
- builder.add(lookupMethod(item));
- }
- } else {
- for (DexMethod item : original) {
- builder.addAll(lookupMethodInAllContexts(item));
- }
+ for (DexType type : types) {
+ builder.add(lookupType(type));
}
return builder.build();
}
- public static <T extends DexReference, S> ImmutableMap<T, S> rewriteReferenceKeys(
- Map<T, S> original, Function<T, T> rewrite) {
- ImmutableMap.Builder<T, S> builder = ImmutableMap.builder();
- original.forEach((item, value) -> builder.put(rewrite.apply(item), value));
+ public <T> ImmutableMap<DexType, T> rewriteTypeKeys(Map<DexType, T> map) {
+ ImmutableMap.Builder<DexType, T> builder = ImmutableMap.builder();
+ map.forEach((type, value) -> builder.put(lookupType(type), value));
return builder.build();
}
@@ -380,51 +383,13 @@
continue;
}
DexMethod originalMethod = getOriginalMethodSignature(method.method);
- assert originalMethods.contains(originalMethod)
- || verifyIsBridgeMethod(
- originalMethod, originalApplication, originalMethods, dexItemFactory)
- : "Unable to map method `"
- + originalMethod.toSourceString()
- + "` back to original program";
+ assert originalMethods.contains(originalMethod);
}
}
return true;
}
- // Check if `method` is a bridge method for a method that is in the original application.
- // This is needed because member rebinding synthesizes bridge methods for visibility.
- private static boolean verifyIsBridgeMethod(
- DexMethod method,
- DexApplication originalApplication,
- Set<DexMethod> originalMethods,
- DexItemFactory dexItemFactory) {
- Deque<DexType> worklist = new ArrayDeque<>();
- Set<DexType> visited = Sets.newIdentityHashSet();
- worklist.add(method.holder);
- while (!worklist.isEmpty()) {
- DexType holder = worklist.removeFirst();
- if (!visited.add(holder)) {
- // Already visited previously.
- continue;
- }
- DexMethod targetMethod = dexItemFactory.createMethod(holder, method.proto, method.name);
- if (originalMethods.contains(targetMethod)) {
- return true;
- }
- // Stop traversing upwards if we reach the Object.
- if (holder == dexItemFactory.objectType) {
- continue;
- }
- DexClass clazz = originalApplication.definitionFor(holder);
- if (clazz != null) {
- worklist.add(clazz.superType);
- Collections.addAll(worklist, clazz.interfaces.values);
- }
- }
- return false;
- }
-
private static class IdentityGraphLense extends GraphLense {
private static IdentityGraphLense INSTANCE = new IdentityGraphLense();
@@ -728,15 +693,6 @@
}
@Override
- public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
- Set<DexMethod> result = Sets.newIdentityHashSet();
- for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
- result.add(methodMap.getOrDefault(previous, previous));
- }
- return result;
- }
-
- @Override
public DexField lookupField(DexField field) {
DexField previous = previousLense.lookupField(field);
return fieldMap.getOrDefault(previous, previous);
diff --git a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
index b40f328..74f9880 100644
--- a/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
+++ b/src/main/java/com/android/tools/r8/graph/InnerClassAttribute.java
@@ -85,8 +85,8 @@
DexType context = getOuter();
if (context == null) {
DexClass inner = appInfo.definitionFor(getInner());
- if (inner != null && inner.getEnclosingMethod() != null) {
- EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethod();
+ if (inner != null && inner.getEnclosingMethodAttribute() != null) {
+ EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethodAttribute();
if (enclosingMethodAttribute.getEnclosingClass() != null) {
context = enclosingMethodAttribute.getEnclosingClass();
} else {
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 1f04cca..7936e33 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -110,7 +110,7 @@
if (application.options.getProguardConfiguration() != null) {
ProguardKeepAttributes keep =
application.options.getProguardConfiguration().getKeepAttributes();
- if (!keep.sourceFile && !keep.sourceDebugExtension) {
+ if (!keep.sourceFile && !keep.sourceDebugExtension && !keep.methodParameters) {
parsingOptions |= SKIP_DEBUG;
}
}
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 06f8c42..6c93947 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -1008,8 +1008,9 @@
|| keep.localVariableTypeTable
|| reachabilitySensitive;
boolean lineInfo = keep.lineNumberTable;
+ boolean methodParaeters = keep.methodParameters;
- if (!localsInfo && !lineInfo) {
+ if (!localsInfo && !lineInfo && !methodParaeters) {
parsingOptions |= ClassReader.SKIP_DEBUG;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index fbb42a9..01d86a1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -7,8 +7,10 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -106,8 +108,9 @@
}
AbstractValue abstractValue = root.getAbstractValue(appView, context);
if (abstractValue.isSingleFieldValue()) {
- DexEncodedField field =
- appView.definitionFor(abstractValue.asSingleFieldValue().getField());
+ DexField fieldReference = abstractValue.asSingleFieldValue().getField();
+ DexClass holder = appView.definitionForHolder(fieldReference);
+ DexEncodedField field = fieldReference.lookupOnClass(holder);
if (field != null && field.isEnum()) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
index be72b24..54d5160 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/constant/SparseConditionalConstantPropagation.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.analysis.constant;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
@@ -14,6 +16,7 @@
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.StringSwitch;
import com.android.tools.r8.ir.code.Value;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Deque;
@@ -21,6 +24,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Implementation of Sparse Conditional Constant Propagation from the paper of Wegman and Zadeck
@@ -29,6 +33,7 @@
*/
public class SparseConditionalConstantPropagation {
+ private final AppView<?> appView;
private final IRCode code;
private final Map<Value, LatticeElement> mapping = new HashMap<>();
private final Deque<Value> ssaEdges = new LinkedList<>();
@@ -37,7 +42,8 @@
private final BitSet[] executableFlowEdges;
private final BitSet visitedBlocks;
- public SparseConditionalConstantPropagation(IRCode code) {
+ public SparseConditionalConstantPropagation(AppView<?> appView, IRCode code) {
+ this.appView = appView;
this.code = code;
nextBlockNumber = code.getHighestBlockNumber() + 1;
executableFlowEdges = new BitSet[nextBlockNumber];
@@ -45,7 +51,6 @@
}
public void run() {
-
BasicBlock firstBlock = code.entryBlock();
visitInstructions(firstBlock);
@@ -77,8 +82,8 @@
}
private void rewriteCode() {
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
List<BasicBlock> blockToAnalyze = new ArrayList<>();
-
mapping.entrySet().stream()
.filter(entry -> entry.getValue().isConst())
.forEach(
@@ -86,6 +91,7 @@
Value value = entry.getKey();
ConstNumber evaluatedConst = entry.getValue().asConst().getConstNumber();
if (value.definition != evaluatedConst) {
+ value.addAffectedValuesTo(affectedValues);
if (value.isPhi()) {
// D8 relies on dead code removal to get rid of the dead phi itself.
if (value.hasAnyUsers()) {
@@ -112,11 +118,12 @@
}
}
});
-
for (BasicBlock block : blockToAnalyze) {
block.deduplicatePhis();
}
-
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
code.removeAllDeadAndTrivialPhis();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index fbb6443..1481061 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -64,8 +64,13 @@
FieldAssignmentTracker(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
- this.fieldAccessGraph = new FieldAccessGraph(appView);
- this.objectAllocationGraph = new ObjectAllocationGraph(appView);
+ this.fieldAccessGraph = new FieldAccessGraph();
+ this.objectAllocationGraph = new ObjectAllocationGraph();
+ }
+
+ public void initialize() {
+ fieldAccessGraph.initialize(appView);
+ objectAllocationGraph.initialize(appView);
initializeAbstractInstanceFieldValues();
}
@@ -308,10 +313,11 @@
private final Reference2IntMap<DexEncodedField> pendingFieldWrites =
new Reference2IntOpenHashMap<>();
- FieldAccessGraph(AppView<AppInfoWithLiveness> appView) {
+ FieldAccessGraph() {}
+
+ public void initialize(AppView<AppInfoWithLiveness> appView) {
FieldAccessInfoCollection<?> fieldAccessInfoCollection =
appView.appInfo().getFieldAccessInfoCollection();
- fieldAccessInfoCollection.flattenAccessContexts();
fieldAccessInfoCollection.forEach(
info -> {
DexEncodedField field =
@@ -356,7 +362,9 @@
private final Reference2IntMap<DexProgramClass> pendingObjectAllocations =
new Reference2IntOpenHashMap<>();
- ObjectAllocationGraph(AppView<AppInfoWithLiveness> appView) {
+ ObjectAllocationGraph() {}
+
+ public void initialize(AppView<AppInfoWithLiveness> appView) {
ObjectAllocationInfoCollection objectAllocationInfos =
appView.appInfo().getObjectAllocationInfoCollection();
objectAllocationInfos.forEachClassWithKnownAllocationSites(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
index a2a932c..9fe885d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -5,7 +5,9 @@
package com.android.tools.r8.ir.analysis.fieldvalueanalysis;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.SetUtils;
@@ -71,12 +73,14 @@
assert !isEmpty();
ConcreteMutableFieldSet rewrittenSet = new ConcreteMutableFieldSet();
for (DexEncodedField field : fields) {
- DexEncodedField rewrittenField = appView.definitionFor(lens.lookupField(field.field));
+ DexField rewrittenFieldReference = lens.lookupField(field.field);
+ DexClass holder = appView.definitionForHolder(rewrittenFieldReference);
+ DexEncodedField rewrittenField = rewrittenFieldReference.lookupOnClass(holder);
if (rewrittenField == null) {
assert false;
continue;
}
- rewrittenSet.add(field);
+ rewrittenSet.add(rewrittenField);
}
return rewrittenSet;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index c64dff4..667c1ff 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
@@ -26,6 +25,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.TreePrunerConfiguration;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
@@ -190,7 +190,7 @@
resolutionResult.asSuccessfulResolution().getResolutionPair().asProgramField();
return field != null
&& isDeadProtoExtensionField(
- field, appInfo.getFieldAccessInfoCollection(), appInfo.getPinnedItems());
+ field, appInfo.getFieldAccessInfoCollection(), appInfo.getKeepInfo());
}
return false;
}
@@ -198,8 +198,8 @@
public boolean isDeadProtoExtensionField(
ProgramField field,
FieldAccessInfoCollection<?> fieldAccessInfoCollection,
- Set<DexReference> pinnedItems) {
- if (pinnedItems.contains(field.getReference())) {
+ KeepInfoCollection keepInfo) {
+ if (keepInfo.getFieldInfo(field).isPinned()) {
return false;
}
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 deadfa0..c589048 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
@@ -4,41 +4,48 @@
package com.android.tools.r8.ir.analysis.proto;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
-import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeVirtual;
-import com.android.tools.r8.ir.code.Switch;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CallGraph.Node;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
-import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
-import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.ObjectUtils;
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.IdentityHashMap;
@@ -53,6 +60,7 @@
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final ProtoReferences references;
+ private final boolean enableAggressiveBuilderOptimization;
private final Map<DexProgramClass, ProgramMethod> builders = new IdentityHashMap<>();
@@ -60,11 +68,98 @@
AppView<? extends AppInfoWithClassHierarchy> appView, ProtoReferences references) {
this.appView = appView;
this.references = references;
+ this.enableAggressiveBuilderOptimization = computeEnableAggressiveBuilderOptimization();
+ // If this fails it is likely an unsupported version of the protobuf library.
+ assert enableAggressiveBuilderOptimization;
+ }
+
+ private boolean computeEnableAggressiveBuilderOptimization() {
+ boolean unexpectedGeneratedMessageLiteBuilder =
+ ObjectUtils.getBooleanOrElse(
+ appView
+ .appInfo()
+ .definitionForWithoutExistenceAssert(references.generatedMessageLiteBuilderType),
+ clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
+ true);
+ boolean unexpectedGeneratedMessageLiteExtendableBuilder =
+ ObjectUtils.getBooleanOrElse(
+ appView
+ .appInfo()
+ .definitionForWithoutExistenceAssert(
+ references.generatedMessageLiteExtendableBuilderType),
+ clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
+ true);
+ if (unexpectedGeneratedMessageLiteBuilder) {
+ appView
+ .options()
+ .reporter
+ .warning(
+ "Unexpected implementation of `"
+ + references.generatedMessageLiteBuilderType.toSourceString()
+ + "`: disabling aggressive protobuf builder optimization.");
+ return false;
+ }
+ if (unexpectedGeneratedMessageLiteExtendableBuilder) {
+ appView
+ .options()
+ .reporter
+ .warning(
+ "Unexpected implementation of `"
+ + references.generatedMessageLiteExtendableBuilderType.toSourceString()
+ + "`: disabling aggressive protobuf builder optimization.");
+ return false;
+ }
+ return true;
+ }
+
+ public EnqueuerAnalysis createEnqueuerAnalysis() {
+ Set<DexProgramClass> seen = Sets.newIdentityHashSet();
+ return new EnqueuerAnalysis() {
+ @Override
+ public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {
+ builders.forEach(
+ (builder, dynamicMethod) -> {
+ if (seen.add(builder)) {
+ // This builder class is never used in the program except from dynamicMethod(),
+ // which creates an instance of the builder. Instead of creating an instance of the
+ // builder class, we just instantiate the parent builder class. For this to work,
+ // we make the parent builder non-abstract.
+ DexProgramClass superClass =
+ asProgramClassOrNull(appView.definitionFor(builder.superType));
+ assert superClass != null;
+ superClass.accessFlags.demoteFromAbstract();
+ if (superClass.type == references.generatedMessageLiteBuilderType) {
+ // Manually trace `new GeneratedMessageLite.Builder(DEFAULT_INSTANCE)` since we
+ // haven't rewritten the code yet.
+ worklist.enqueueTraceNewInstanceAction(
+ references.generatedMessageLiteBuilderType, dynamicMethod);
+ worklist.enqueueTraceInvokeDirectAction(
+ references.generatedMessageLiteBuilderMethods.constructorMethod,
+ dynamicMethod);
+ } else {
+ assert superClass.type == references.generatedMessageLiteExtendableBuilderType;
+ // Manually trace `new GeneratedMessageLite.ExtendableBuilder(DEFAULT_INSTANCE)`
+ // since we haven't rewritten the code yet.
+ worklist.enqueueTraceNewInstanceAction(
+ references.generatedMessageLiteExtendableBuilderType, dynamicMethod);
+ worklist.enqueueTraceInvokeDirectAction(
+ references.generatedMessageLiteExtendableBuilderMethods.constructorMethod,
+ dynamicMethod);
+ }
+ worklist.enqueueTraceStaticFieldRead(
+ references.getDefaultInstanceField(dynamicMethod.getHolder()), dynamicMethod);
+ }
+ });
+ }
+ };
}
/** Returns true if an action was deferred. */
public boolean deferDeadProtoBuilders(
DexProgramClass clazz, ProgramMethod method, BooleanSupplier register) {
+ if (!enableAggressiveBuilderOptimization) {
+ return false;
+ }
DexEncodedMethod definition = method.getDefinition();
if (references.isDynamicMethod(definition) && references.isGeneratedMessageLiteBuilder(clazz)) {
if (register.getAsBoolean()) {
@@ -77,40 +172,79 @@
}
/**
- * Reprocesses each dynamicMethod() that references a dead builder to remove the dead builder
+ * Reprocesses each dynamicMethod() that references a dead builder to rewrite the dead builder
* references.
*/
- public void removeDeadBuilderReferencesFromDynamicMethods(
+ public void rewriteDeadBuilderReferencesFromDynamicMethods(
AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
+ if (builders.isEmpty()) {
+ return;
+ }
timing.begin("Remove dead builder references");
AppInfoWithLiveness appInfo = appView.appInfo();
IRConverter converter = new IRConverter(appView, Timing.empty());
- CodeRewriter codeRewriter = new CodeRewriter(appView, converter);
- MethodToInvokeSwitchCaseAnalyzer switchCaseAnalyzer = new MethodToInvokeSwitchCaseAnalyzer();
- if (switchCaseAnalyzer.isInitialized()) {
- ThreadUtils.processItems(
- builders.entrySet(),
- entry -> {
- if (!appInfo.isLiveProgramClass(entry.getKey())) {
- removeDeadBuilderReferencesFromDynamicMethod(
- appView, entry.getValue(), converter, codeRewriter, switchCaseAnalyzer);
- }
- },
- executorService);
- }
+ ThreadUtils.processMap(
+ builders,
+ (builder, dynamicMethod) -> {
+ if (!appInfo.isLiveProgramClass(builder)) {
+ rewriteDeadBuilderReferencesFromDynamicMethod(
+ appView, builder, dynamicMethod, converter);
+ }
+ },
+ executorService);
builders.clear();
timing.end(); // Remove dead builder references
}
- private void removeDeadBuilderReferencesFromDynamicMethod(
+ private void rewriteDeadBuilderReferencesFromDynamicMethod(
AppView<AppInfoWithLiveness> appView,
+ DexProgramClass builder,
ProgramMethod dynamicMethod,
- IRConverter converter,
- CodeRewriter codeRewriter,
- SwitchCaseAnalyzer switchCaseAnalyzer) {
+ IRConverter converter) {
IRCode code = dynamicMethod.buildIR(appView);
- codeRewriter.rewriteSwitch(code, switchCaseAnalyzer);
+ InstructionListIterator instructionIterator = code.instructionListIterator();
+
+ assert builder.superType == references.generatedMessageLiteBuilderType
+ || builder.superType == references.generatedMessageLiteExtendableBuilderType;
+
+ DexField defaultInstanceField = references.getDefaultInstanceField(dynamicMethod.getHolder());
+ Value builderValue =
+ code.createValue(ClassTypeElement.create(builder.superType, definitelyNotNull(), appView));
+ Value defaultInstanceValue =
+ code.createValue(ClassTypeElement.create(defaultInstanceField.type, maybeNull(), appView));
+
+ // Replace `new Message.Builder()` by `new GeneratedMessageLite.Builder()`
+ // (or `new GeneratedMessageLite.ExtendableBuilder()`).
+ NewInstance newInstance =
+ instructionIterator.nextUntil(
+ instruction ->
+ instruction.isNewInstance() && instruction.asNewInstance().clazz == builder.type);
+ assert newInstance != null;
+ instructionIterator.replaceCurrentInstruction(new NewInstance(builder.superType, builderValue));
+
+ // Replace `builder.<init>()` by `builder.<init>(Message.DEFAULT_INSTANCE)`.
+ //
+ // We may also see an accessibility bridge constructor, because the Builder constructor is
+ // private. The accessibility bridge takes null as an argument.
+ InvokeDirect constructorInvoke =
+ instructionIterator.nextUntil(
+ instruction -> {
+ assert instruction.isInvokeDirect() || instruction.isConstNumber();
+ return instruction.isInvokeDirect();
+ });
+ assert constructorInvoke != null;
+ instructionIterator.replaceCurrentInstruction(
+ new StaticGet(defaultInstanceValue, defaultInstanceField));
+ instructionIterator.setInsertionPosition(constructorInvoke.getPosition());
+ instructionIterator.add(
+ new InvokeDirect(
+ builder.superType == references.generatedMessageLiteBuilderType
+ ? references.generatedMessageLiteBuilderMethods.constructorMethod
+ : references.generatedMessageLiteExtendableBuilderMethods.constructorMethod,
+ null,
+ ImmutableList.of(builderValue, defaultInstanceValue)));
+
converter.removeDeadCodeAndFinalizeIR(
dynamicMethod, code, OptimizationFeedbackSimple.getInstance(), Timing.empty());
}
@@ -232,51 +366,6 @@
}
}
- private class MethodToInvokeSwitchCaseAnalyzer extends SwitchCaseAnalyzer {
-
- private final int newBuilderOrdinal;
-
- private MethodToInvokeSwitchCaseAnalyzer() {
- EnumValueInfoMap enumValueInfoMap =
- appView.appInfo().withLiveness().getEnumValueInfoMap(references.methodToInvokeType);
- if (enumValueInfoMap != null) {
- EnumValueInfo newBuilderValueInfo =
- enumValueInfoMap.getEnumValueInfo(references.methodToInvokeMembers.newBuilderField);
- if (newBuilderValueInfo != null) {
- newBuilderOrdinal = newBuilderValueInfo.ordinal;
- return;
- }
- }
- newBuilderOrdinal = -1;
- }
-
- public boolean isInitialized() {
- return newBuilderOrdinal >= 0;
- }
-
- @Override
- public boolean switchCaseIsUnreachable(Switch theSwitch, int index) {
- if (theSwitch.isStringSwitch()) {
- assert false : "Unexpected string-switch instruction in dynamicMethod()";
- return false;
- }
- if (index != newBuilderOrdinal) {
- return false;
- }
- Value switchValue = theSwitch.value();
- if (!switchValue.isDefinedByInstructionSatisfying(Instruction::isInvokeVirtual)) {
- return false;
- }
- InvokeVirtual definition = switchValue.definition.asInvokeVirtual();
- if (definition.getInvokedMethod() != appView.dexItemFactory().enumMethods.ordinal) {
- return false;
- }
- TypeElement enumType = definition.getReceiver().getType();
- return enumType.isClassType()
- && enumType.asClassType().getClassType() == references.methodToInvokeType;
- }
- }
-
private static class RootSetExtension {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
@@ -305,8 +394,9 @@
void extend(SubtypingInfo subtypingInfo) {
alwaysClassInlineGeneratedMessageLiteBuilders();
- // GeneratedMessageLite heuristics.
+ // MessageLite and GeneratedMessageLite heuristics.
alwaysInlineCreateBuilderFromGeneratedMessageLite();
+ neverMergeMessageLite();
// * extends GeneratedMessageLite heuristics.
bypassClinitforInliningNewBuilderMethods(subtypingInfo);
@@ -353,5 +443,11 @@
neverMerge.add(references.generatedMessageLiteBuilderType);
neverMerge.add(references.generatedMessageLiteExtendableBuilderType);
}
+
+ private void neverMergeMessageLite() {
+ // MessageLite is used in several signatures that we use for recognizing methods, so don't
+ // allow it to me merged.
+ neverMerge.add(references.messageLiteType);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
index ca4ca1e..e8770c7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -43,11 +43,19 @@
return Reason.ALWAYS;
}
return references.isDynamicMethod(target) || references.isDynamicMethodBridge(target)
- ? computeInliningReasonForDynamicMethod(invoke)
+ ? computeInliningReasonForDynamicMethod(invoke, target, context)
: parent.computeInliningReason(invoke, target, context);
}
- private Reason computeInliningReasonForDynamicMethod(InvokeMethod invoke) {
+ private Reason computeInliningReasonForDynamicMethod(
+ InvokeMethod invoke, ProgramMethod target, ProgramMethod context) {
+ // Do not allow inlining of dynamicMethod() into a proto library class. This should only happen
+ // if there is exactly one proto message in the program, since we would otherwise not be able
+ // to conclude a single target.
+ if (references.isDynamicMethod(target) && references.isProtoLibraryClass(context.getHolder())) {
+ return Reason.NEVER;
+ }
+
Value methodToInvokeValue =
invoke
.inValues()
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
index 861c0cd..8fd43ee 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoReferences.java
@@ -17,6 +17,8 @@
public class ProtoReferences {
+ private final DexItemFactory dexItemFactory;
+
public final DexType enumLiteMapType;
public final DexType extendableMessageType;
public final DexType extensionDescriptorType;
@@ -25,6 +27,7 @@
public final DexType generatedMessageLiteType;
public final DexType generatedMessageLiteBuilderType;
public final DexType generatedMessageLiteExtendableBuilderType;
+ public final DexType generatedMessageLiteExtendableMessageType;
public final DexType rawMessageInfoType;
public final DexType messageLiteType;
public final DexType methodToInvokeType;
@@ -37,10 +40,13 @@
generatedMessageLiteExtendableBuilderMethods;
public final MethodToInvokeMembers methodToInvokeMembers;
+ public final DexString defaultInstanceFieldName;
public final DexString dynamicMethodName;
public final DexString findLiteExtensionByNumberName;
public final DexString newBuilderMethodName;
+ public final DexString protobufPackageDescriptorPrefix;
+
public final DexProto dynamicMethodProto;
public final DexProto findLiteExtensionByNumberProto;
@@ -49,6 +55,8 @@
public final DexMethod rawMessageInfoConstructor;
public ProtoReferences(DexItemFactory factory) {
+ dexItemFactory = factory;
+
// Types.
enumLiteMapType = factory.createType("Lcom/google/protobuf/Internal$EnumLiteMap;");
extendableMessageType =
@@ -63,6 +71,8 @@
factory.createType("Lcom/google/protobuf/GeneratedMessageLite$Builder;");
generatedMessageLiteExtendableBuilderType =
factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableBuilder;");
+ generatedMessageLiteExtendableMessageType =
+ factory.createType("Lcom/google/protobuf/GeneratedMessageLite$ExtendableMessage;");
rawMessageInfoType = factory.createType("Lcom/google/protobuf/RawMessageInfo;");
messageLiteType = factory.createType("Lcom/google/protobuf/MessageLite;");
methodToInvokeType =
@@ -70,10 +80,14 @@
wireFormatFieldType = factory.createType("Lcom/google/protobuf/WireFormat$FieldType;");
// Names.
+ defaultInstanceFieldName = factory.createString("DEFAULT_INSTANCE");
dynamicMethodName = factory.createString("dynamicMethod");
findLiteExtensionByNumberName = factory.createString("findLiteExtensionByNumber");
newBuilderMethodName = factory.createString("newBuilder");
+ // Other names.
+ protobufPackageDescriptorPrefix = factory.createString("Lcom/google/protobuf/");
+
// Protos.
dynamicMethodProto =
factory.createProto(
@@ -105,6 +119,10 @@
methodToInvokeMembers = new MethodToInvokeMembers(factory);
}
+ public DexField getDefaultInstanceField(DexProgramClass holder) {
+ return dexItemFactory.createField(holder.type, holder.type, defaultInstanceFieldName);
+ }
+
public boolean isAbstractGeneratedMessageLiteBuilder(DexProgramClass clazz) {
return clazz.type == generatedMessageLiteBuilderType
|| clazz.type == generatedMessageLiteExtendableBuilderType;
@@ -123,7 +141,8 @@
}
public boolean isDynamicMethodBridge(DexMethod method) {
- return method == generatedMessageLiteMethods.dynamicMethodBridgeMethod;
+ return method == generatedMessageLiteMethods.dynamicMethodBridgeMethod
+ || method == generatedMessageLiteMethods.dynamicMethodBridgeMethodWithObject;
}
public boolean isDynamicMethodBridge(DexEncodedMethod method) {
@@ -154,6 +173,10 @@
return method.match(newMessageInfoMethod) || method == rawMessageInfoConstructor;
}
+ public boolean isProtoLibraryClass(DexProgramClass clazz) {
+ return clazz.type.descriptor.startsWith(protobufPackageDescriptorPrefix);
+ }
+
public class GeneratedExtensionMethods {
public final DexMethod constructor;
@@ -192,6 +215,7 @@
public final DexMethod createBuilderMethod;
public final DexMethod dynamicMethodBridgeMethod;
+ public final DexMethod dynamicMethodBridgeMethodWithObject;
public final DexMethod isInitializedMethod;
public final DexMethod newRepeatedGeneratedExtension;
public final DexMethod newSingularGeneratedExtension;
@@ -207,6 +231,12 @@
generatedMessageLiteType,
dexItemFactory.createProto(dexItemFactory.objectType, methodToInvokeType),
"dynamicMethod");
+ dynamicMethodBridgeMethodWithObject =
+ dexItemFactory.createMethod(
+ generatedMessageLiteType,
+ dexItemFactory.createProto(
+ dexItemFactory.objectType, methodToInvokeType, dexItemFactory.objectType),
+ "dynamicMethod");
isInitializedMethod =
dexItemFactory.createMethod(
generatedMessageLiteType,
@@ -241,7 +271,7 @@
}
}
- class GeneratedMessageLiteBuilderMethods {
+ public class GeneratedMessageLiteBuilderMethods {
public final DexMethod buildPartialMethod;
public final DexMethod constructorMethod;
@@ -260,9 +290,10 @@
}
}
- class GeneratedMessageLiteExtendableBuilderMethods {
+ public class GeneratedMessageLiteExtendableBuilderMethods {
public final DexMethod buildPartialMethod;
+ public final DexMethod constructorMethod;
private GeneratedMessageLiteExtendableBuilderMethods(DexItemFactory dexItemFactory) {
buildPartialMethod =
@@ -270,6 +301,12 @@
generatedMessageLiteExtendableBuilderType,
dexItemFactory.createProto(extendableMessageType),
"buildPartial");
+ constructorMethod =
+ dexItemFactory.createMethod(
+ generatedMessageLiteExtendableBuilderType,
+ dexItemFactory.createProto(
+ dexItemFactory.voidType, generatedMessageLiteExtendableMessageType),
+ dexItemFactory.constructorMethodName);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
index 3f0d75a..ed2cdd8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
public class AlwaysMaterializingDefinition extends ConstInstruction {
@@ -29,9 +30,9 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
// This instruction may never be considered dead as it must remain.
- return false;
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
index 3cd25ea..45fa439 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -30,8 +31,8 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return false;
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index 4b99975..21b02ba 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -29,9 +30,9 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
// This instruction may never be considered dead as it must remain.
- return false;
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 5b68bf5..fbca901 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.Set;
@@ -62,11 +63,11 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appview, IRCode code) {
+ public DeadInstructionResult canBeDeadCode(AppView<?> appview, IRCode code) {
// Never remove argument instructions. That would change the signature of the method.
// TODO(b/65810338): If we can tell that a method never uses an argument we might be able to
// rewrite the signature and call-sites.
- return false;
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index e9f074b..4f0995b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -84,11 +84,6 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !instructionMayHaveSideEffects(appView, code.context());
- }
-
- @Override
public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
if (super.identicalAfterRegisterAllocation(other, allocator)) {
// The array length instruction doesn't carry the element type. The art verifier doesn't
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 860473c..9dae2a8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -191,11 +191,6 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !instructionMayHaveSideEffects(appView, code.context());
- }
-
- @Override
public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
// We cannot share ArrayPut instructions without knowledge of the type of the array input.
// If multiple primitive array types flow to the same ArrayPut instruction the art verifier
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 117af7b..7aed1d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
@@ -16,7 +15,6 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.utils.BooleanUtils;
import java.util.Objects;
import java.util.Set;
@@ -25,11 +23,11 @@
private static final String ERROR_MESSAGE =
"Expected Assume instructions to be removed after IR processing.";
- private final DynamicTypeAssumption dynamicTypeAssumption;
+ private DynamicTypeAssumption dynamicTypeAssumption;
private final NonNullAssumption nonNullAssumption;
private final Instruction origin;
- private Assume(
+ public Assume(
DynamicTypeAssumption dynamicTypeAssumption,
NonNullAssumption nonNullAssumption,
Value dest,
@@ -38,9 +36,6 @@
AppView<?> appView) {
super(dest, src);
assert dynamicTypeAssumption != null || nonNullAssumption != null;
- assert BooleanUtils.intValue(dynamicTypeAssumption != null)
- + BooleanUtils.intValue(nonNullAssumption != null)
- == 1;
assert dynamicTypeAssumption == null
|| dynamicTypeAssumption.verifyCorrectnessOfValues(dest, src, appView);
assert nonNullAssumption == null
@@ -78,7 +73,7 @@
}
public boolean verifyInstructionIsNeeded(AppView<?> appView) {
- if (isAssumeDynamicType()) {
+ if (hasDynamicTypeAssumption()) {
assert dynamicTypeAssumption.verifyCorrectnessOfValues(outValue(), src(), appView);
}
return true;
@@ -112,13 +107,7 @@
@Override
public String getInstructionName() {
- if (isAssumeDynamicType()) {
- return "AssumeDynamicType";
- }
- if (isAssumeNonNull()) {
- return "AssumeNonNull";
- }
- throw new Unimplemented();
+ return "Assume";
}
@Override
@@ -131,13 +120,15 @@
return this;
}
- @Override
- public boolean isAssumeDynamicType() {
+ public boolean hasDynamicTypeAssumption() {
return dynamicTypeAssumption != null;
}
- @Override
- public boolean isAssumeNonNull() {
+ public void unsetDynamicTypeAssumption() {
+ dynamicTypeAssumption = null;
+ }
+
+ public boolean hasNonNullAssumption() {
return nonNullAssumption != null;
}
@@ -149,7 +140,7 @@
if (outType.isPrimitiveType()) {
return false;
}
- if (isAssumeDynamicType()) {
+ if (hasDynamicTypeAssumption()) {
outType = dynamicTypeAssumption.getDynamicUpperBoundType();
}
if (appView.appInfo().hasLiveness()) {
@@ -220,14 +211,11 @@
@Override
public TypeElement evaluate(AppView<?> appView) {
- if (isAssumeDynamicType()) {
- return src().getType();
- }
- if (isAssumeNonNull()) {
+ if (hasNonNullAssumption()) {
assert src().getType().isReferenceType();
return src().getType().asReferenceType().asMeetWithNotNull();
}
- throw new Unimplemented();
+ return src().getType();
}
@Override
@@ -255,15 +243,14 @@
assert super.verifyTypes(appView);
TypeElement inType = src().getType();
+ assert inType.isReferenceType() : inType;
+
TypeElement outType = getOutType();
- if (isAssumeDynamicType()) {
- assert inType.isReferenceType() : inType;
- assert outType.equals(inType)
+ if (hasNonNullAssumption()) {
+ assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
: "At " + this + System.lineSeparator() + outType + " != " + inType;
} else {
- assert isAssumeNonNull() : this;
- assert inType.isReferenceType() : inType;
- assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
+ assert outType.equals(inType)
: "At " + this + System.lineSeparator() + outType + " != " + inType;
}
return true;
@@ -276,21 +263,21 @@
// assumption became "truth."
// 2) invoke-interface could be devirtualized, while its dynamic type and/or non-null receiver
// are still valid.
- String originString =
- origin.hasBlock() ? " (origin: `" + origin.toString() + "`)" : " (obsolete origin)";
- if (isAssumeNonNull()) {
- return super.toString() + originString;
+ StringBuilder builder = new StringBuilder(super.toString());
+ if (hasNonNullAssumption()) {
+ builder.append("; not null");
}
- if (isAssumeDynamicType()) {
- return super.toString()
- + "; upper bound: "
- + dynamicTypeAssumption.dynamicUpperBoundType
- + (dynamicTypeAssumption.dynamicLowerBoundType != null
- ? "; lower bound: " + dynamicTypeAssumption.dynamicLowerBoundType
- : "")
- + originString;
+ if (hasDynamicTypeAssumption()) {
+ if (hasOutValue()) {
+ if (!dynamicTypeAssumption.dynamicUpperBoundType.equalUpToNullability(outValue.getType())) {
+ builder.append("; upper bound: ").append(dynamicTypeAssumption.dynamicUpperBoundType);
+ }
+ }
+ if (dynamicTypeAssumption.dynamicLowerBoundType != null) {
+ builder.append("; lower bound: ").append(dynamicTypeAssumption.dynamicLowerBoundType);
+ }
}
- return super.toString();
+ return builder.toString();
}
public static class DynamicTypeAssumption {
@@ -298,7 +285,7 @@
private final TypeElement dynamicUpperBoundType;
private final ClassTypeElement dynamicLowerBoundType;
- private DynamicTypeAssumption(
+ public DynamicTypeAssumption(
TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
this.dynamicUpperBoundType = dynamicUpperBoundType;
this.dynamicLowerBoundType = dynamicLowerBoundType;
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 26b0acd..b0ef472 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
@@ -1292,36 +1292,17 @@
builder.append(": ");
builder.append(instruction.toString());
if (DebugLocalInfo.PRINT_LEVEL != PrintLevel.NONE) {
- List<Value> localEnds = new ArrayList<>(instruction.getDebugValues().size());
- List<Value> localStarts = new ArrayList<>(instruction.getDebugValues().size());
- List<Value> localLive = new ArrayList<>(instruction.getDebugValues().size());
- for (Value value : instruction.getDebugValues()) {
- if (value.getDebugLocalEnds().contains(instruction)) {
- localEnds.add(value);
- } else if (value.getDebugLocalStarts().contains(instruction)) {
- localStarts.add(value);
- } else {
- assert value.debugUsers().contains(instruction);
- localLive.add(value);
- }
+ if (!instruction.getDebugValues().isEmpty()) {
+ builder.append(" [end: ");
+ StringUtils.append(builder, instruction.getDebugValues(), ", ", BraceType.NONE);
+ builder.append("]");
}
- printDebugValueSet("live", localLive, builder);
- printDebugValueSet("end", localEnds, builder);
- printDebugValueSet("start", localStarts, builder);
}
builder.append("\n");
}
return builder.toString();
}
- private void printDebugValueSet(String header, List<Value> locals, StringBuilder builder) {
- if (!locals.isEmpty()) {
- builder.append(" [").append(header).append(": ");
- StringUtils.append(builder, locals, ", ", BraceType.NONE);
- builder.append("]");
- }
- }
-
public void print(CfgPrinter printer) {
printer.begin("block");
printer.print("name \"B").append(number).append("\"\n");
@@ -1990,15 +1971,34 @@
Phi phi = phiIt.next();
Wrapper<Phi> key = equivalence.wrap(phi);
Phi replacement = wrapper2phi.get(key);
- if (replacement != null) {
- phi.replaceUsers(replacement);
- for (Value operand : phi.getOperands()) {
- operand.removePhiUser(phi);
- }
- phiIt.remove();
- } else {
+ if (replacement == null) {
wrapper2phi.put(key, phi);
+ continue;
}
+ // Two phis may be duplicates but still differ in debug info.
+ // For example, it may be that the one phi denotes the result of a local pre-increment, while
+ // the other phi represents the modified local, e.g., cond ? ++x : x will give rise to:
+ // v2 <- phi(v0(x), v1(x))
+ // v3(x) <- phi(v0(x), v1(x))
+ // where v2 is used as the result of the expression and v3 is the local slot value of x.
+ // This should be replaced by a single phi.
+ if (phi.getLocalInfo() != replacement.getLocalInfo()) {
+ if (replacement.getLocalInfo() == null) {
+ // The replacement must take over the debug info.
+ replacement.setLocalInfo(phi.getLocalInfo());
+ } else if (phi.getLocalInfo() == null) {
+ // The replacement already owns debug info.
+ } else {
+ // The phis define two distinct locals and cannot be de-duped.
+ assert phi.hasLocalInfo() && replacement.hasLocalInfo();
+ continue;
+ }
+ }
+ phi.replaceUsers(replacement);
+ for (Value operand : phi.getOperands()) {
+ operand.removePhiUser(phi);
+ }
+ phiIt.remove();
}
}
}
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 b4ceb0d..50cec6e 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
@@ -87,10 +87,20 @@
}
@Override
+ public boolean hasInsertionPosition() {
+ return position != null;
+ }
+
+ @Override
public void setInsertionPosition(Position position) {
this.position = position;
}
+ @Override
+ public void unsetInsertionPosition() {
+ this.position = null;
+ }
+
/**
* Adds an instruction to the block. The instruction will be added just before the current cursor
* position.
@@ -198,7 +208,9 @@
}
current.moveDebugValues(newInstruction);
newInstruction.setBlock(block);
- newInstruction.setPosition(current.getPosition());
+ if (!newInstruction.hasPosition()) {
+ newInstruction.setPosition(current.getPosition());
+ }
listIterator.remove();
listIterator.add(newInstruction);
current.clearBlock();
@@ -210,7 +222,15 @@
IRCode code, InternalOptions options, long value, TypeElement type) {
ConstNumber constNumberInstruction = code.createNumberConstant(value, type);
// Note that we only keep position info for throwing instructions in release mode.
- constNumberInstruction.setPosition(options.debug ? current.getPosition() : Position.none());
+ if (!hasInsertionPosition()) {
+ Position position;
+ if (options.debug) {
+ position = current != null ? current.getPosition() : block.getPosition();
+ } else {
+ position = Position.none();
+ }
+ constNumberInstruction.setPosition(position);
+ }
add(constNumberInstruction);
return constNumberInstruction.outValue();
}
@@ -235,12 +255,6 @@
// Replace the instruction by const-number.
ConstNumber constNumber = code.createIntConstant(value, current.getLocalInfo());
- for (Value inValue : current.inValues()) {
- if (inValue.hasLocalInfo()) {
- // Add this value as a debug value to avoid changing its live range.
- constNumber.addDebugValue(inValue);
- }
- }
replaceCurrentInstruction(constNumber);
}
@@ -256,12 +270,6 @@
TypeElement oldType = current.getOutType();
Value value = code.createValue(newType, current.getLocalInfo());
StaticGet staticGet = new StaticGet(value, field);
- for (Value inValue : current.inValues()) {
- if (inValue.hasLocalInfo()) {
- // Add this value as a debug value to avoid changing its live range.
- staticGet.addDebugValue(inValue);
- }
- }
replaceCurrentInstruction(staticGet);
// Update affected values.
@@ -280,58 +288,97 @@
if (current == null) {
throw new IllegalStateException();
}
- BasicBlock block = current.getBlock();
+
+ Instruction toBeReplaced = current;
+
+ BasicBlock block = toBeReplaced.getBlock();
assert !blocksToRemove.contains(block);
assert affectedValues != null;
- BasicBlock normalSuccessorBlock = split(code, blockIterator);
+ // Split the block before the instruction that should be replaced by `throw null`.
previous();
- // Unlink all blocks that are dominated by successor.
- {
- DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
- blocksToRemove.addAll(block.unlink(normalSuccessorBlock, dominatorTree, affectedValues));
+ BasicBlock throwBlock;
+ if (block.hasCatchHandlers() && !toBeReplaced.instructionTypeCanThrow()) {
+ // We need to insert the throw instruction in a block of its own, so split the current block
+ // into three blocks, where the intermediate block only contains a goto instruction.
+ throwBlock = split(code, blockIterator, true);
+ throwBlock.listIterator(code).split(code, blockIterator);
+ } else {
+ split(code, blockIterator, true);
+ throwBlock = block;
}
- // Insert constant null before the instruction.
+ // Position the instruction iterator before the goto instruction.
+ assert !hasNext();
previous();
- Value nullValue = insertConstNullInstruction(code, appView.options());
- next();
+
+ // Unlink all blocks that are dominated by the unique normal successor of the throw block.
+ blocksToRemove.addAll(
+ throwBlock.unlink(
+ throwBlock.getUniqueNormalSuccessor(),
+ new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS),
+ affectedValues));
+
+ InstructionListIterator throwBlockInstructionIterator;
+ if (throwBlock == block) {
+ throwBlockInstructionIterator = this;
+ } else {
+ throwBlockInstructionIterator = throwBlock.listIterator(code);
+ throwBlockInstructionIterator.setInsertionPosition(position);
+ }
+
+ // Insert constant null before the goto instruction.
+ Value nullValue =
+ throwBlockInstructionIterator.insertConstNullInstruction(code, appView.options());
+
+ // Move past the inserted goto instruction.
+ throwBlockInstructionIterator.next();
+ assert !throwBlockInstructionIterator.hasNext();
// Replace the instruction by throw.
Throw throwInstruction = new Throw(nullValue);
- for (Value inValue : current.inValues()) {
- if (inValue.hasLocalInfo()) {
- // Add this value as a debug value to avoid changing its live range.
- throwInstruction.addDebugValue(inValue);
- }
+ if (hasInsertionPosition()) {
+ throwInstruction.setPosition(position);
+ } else if (toBeReplaced.getPosition().isSome()) {
+ throwInstruction.setPosition(toBeReplaced.getPosition());
+ } else {
+ // The instruction that is being removed cannot throw, and thus it must be unreachable as we
+ // are replacing it by `throw null`, so we can safely use a none-position.
+ assert !toBeReplaced.instructionTypeCanThrow();
+ throwInstruction.setPosition(Position.syntheticNone());
}
- replaceCurrentInstruction(throwInstruction);
- next();
- remove();
+ throwBlockInstructionIterator.replaceCurrentInstruction(throwInstruction);
- // Remove all catch handlers where the guard does not include NullPointerException.
if (block.hasCatchHandlers()) {
- CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
- catchHandlers.forEach(
- (guard, target) -> {
- if (blocksToRemove.contains(target)) {
- // Already removed previously. This may happen if two catch handlers have the same
- // target.
- return;
- }
- if (!appView.appInfo().isSubtype(appView.dexItemFactory().npeType, guard)) {
- // TODO(christofferqa): Consider updating previous dominator tree instead of
- // rebuilding it from scratch.
- DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
- blocksToRemove.addAll(block.unlink(target, dominatorTree, affectedValues));
- }
- });
+ if (block == throwBlock) {
+ // Remove all catch handlers where the guard does not include NullPointerException if the
+ // replaced instruction could throw.
+ CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
+ catchHandlers.forEach(
+ (guard, target) -> {
+ if (blocksToRemove.contains(target)) {
+ // Already removed previously. This may happen if two catch handlers have the same
+ // target.
+ return;
+ }
+ if (!appView.appInfo().isSubtype(appView.dexItemFactory().npeType, guard)) {
+ // TODO(christofferqa): Consider updating previous dominator tree instead of
+ // rebuilding it from scratch.
+ DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
+ blocksToRemove.addAll(block.unlink(target, dominatorTree, affectedValues));
+ }
+ });
+ } else {
+ // We replaced a dead, non-throwing instruction by a throwing instruction. Since this is
+ // dead code, we don't need to worry about the catch handlers of the `throwBlock`.
+ }
}
}
@Override
- public BasicBlock split(IRCode code, ListIterator<BasicBlock> blocksIterator) {
+ public BasicBlock split(
+ IRCode code, ListIterator<BasicBlock> blocksIterator, boolean keepCatchHandlers) {
List<BasicBlock> blocks = code.blocks;
assert blocksIterator == null || IteratorUtils.peekPrevious(blocksIterator) == block;
@@ -346,7 +393,6 @@
// Prepare the new block, placing the exception handlers on the block with the throwing
// instruction.
- boolean keepCatchHandlers = hasPrevious() && peekPrevious().instructionTypeCanThrow();
newBlock = block.createSplitBlock(blockNumber, keepCatchHandlers);
// Add a goto instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 4594db9..d28135d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -133,11 +133,6 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !instructionMayHaveSideEffects(appView, code.context());
- }
-
- @Override
public boolean isOutConstant() {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 3bd1d56..0a76de9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfConstString;
@@ -11,13 +13,13 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.io.UTFDataFormatException;
@@ -132,9 +134,12 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
// No side-effect, such as throwing an exception, in CF.
- return appView.options().isGeneratingClassFiles() || !instructionInstanceCanThrow();
+ if (appView.options().isGeneratingClassFiles() || !instructionInstanceCanThrow()) {
+ return DeadInstructionResult.deadIfOutValueIsDead();
+ }
+ return DeadInstructionResult.notDead();
}
@Override
@@ -154,7 +159,7 @@
@Override
public TypeElement evaluate(AppView<?> appView) {
- return TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
+ return TypeElement.stringClassType(appView, definitelyNotNull());
}
@Override
@@ -170,4 +175,12 @@
}
return UnknownValue.getInstance();
}
+
+ @Override
+ public boolean verifyTypes(AppView<?> appView) {
+ assert super.verifyTypes(appView);
+ TypeElement expectedType = TypeElement.stringClassType(appView, definitelyNotNull());
+ assert getOutType().equals(expectedType);
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 292d6b7..5a3e094 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -71,10 +72,10 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
// Reads are never dead code.
// They should also have a non-empty set of debug values (see RegAlloc::computeDebugInfo)
- return false;
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 842eaa4..1ff7509 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.StringUtils;
@@ -83,8 +84,8 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return false;
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index fd6f9a9..c347f87 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -67,8 +68,8 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return false;
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 959dbfc..0753447 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.naming.dexitembasedstring.NameComputationInfo;
@@ -131,9 +132,9 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
// No side-effect, such as throwing an exception, in CF.
- return true;
+ return DeadInstructionResult.deadIfOutValueIsDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index a0af5e2..3ef2eee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -245,10 +245,14 @@
liveStack.addLast(use);
}
}
- assert instruction.getDebugValues().stream().allMatch(Value::needsRegister);
- assert instruction.getDebugValues().stream().allMatch(Value::hasLocalInfo);
- live.addAll(instruction.getDebugValues());
- liveLocals.addAll(instruction.getDebugValues());
+ if (!instruction.getDebugValues().isEmpty()) {
+ ArrayList<Value> sortedValues = new ArrayList<>(instruction.getDebugValues());
+ sortedValues.sort(Value::compareTo);
+ assert sortedValues.stream().allMatch(Value::needsRegister);
+ assert sortedValues.stream().allMatch(Value::hasLocalInfo);
+ live.addAll(sortedValues);
+ liveLocals.addAll(sortedValues);
+ }
}
for (Phi phi : block.getPhis()) {
if (phi.isValueOnStack()) {
@@ -609,17 +613,17 @@
// We can only type check the program if we have subtyping information. Therefore, we do not
// require that the program type checks in D8.
if (appView.enableWholeProgramOptimizations()) {
- assert validAssumeDynamicTypeInstructions(appView);
+ assert validAssumeInstructions(appView);
assert new TypeChecker(appView.withLiveness()).check(this);
}
assert blocks.stream().allMatch(block -> block.verifyTypes(appView));
return true;
}
- private boolean validAssumeDynamicTypeInstructions(AppView<?> appView) {
+ private boolean validAssumeInstructions(AppView<?> appView) {
for (BasicBlock block : blocks) {
for (Instruction instruction : block.getInstructions()) {
- if (instruction.isAssumeDynamicType()) {
+ if (instruction.isAssume()) {
assert instruction.asAssume().verifyInstructionIsNeeded(appView);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index b9cc832..bec911e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -68,7 +68,8 @@
}
@Override
- public BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) {
+ public BasicBlock split(
+ IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) {
throw new Unimplemented();
}
@@ -159,4 +160,19 @@
public void removeOrReplaceByDebugLocalRead() {
instructionIterator.removeOrReplaceByDebugLocalRead();
}
+
+ @Override
+ public boolean hasInsertionPosition() {
+ return instructionIterator.hasInsertionPosition();
+ }
+
+ @Override
+ public void setInsertionPosition(Position position) {
+ instructionIterator.setInsertionPosition(position);
+ }
+
+ @Override
+ public void unsetInsertionPosition() {
+ instructionIterator.unsetInsertionPosition();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
index 9077c97..faebe44 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceFieldInstruction.java
@@ -16,6 +16,9 @@
Value object();
+ boolean instructionInstanceCanThrow(
+ AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption);
+
boolean instructionMayHaveSideEffects(
AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 924d256..7dad747 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -123,16 +123,6 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- // instance-get can be dead code as long as it cannot have any of the following:
- // * NoSuchFieldError (resolution failure)
- // * IncompatibleClassChangeError (instance-* instruction for static fields)
- // * IllegalAccessError (not visible from the access context)
- // * NullPointerException (null receiver)
- return !instructionMayHaveSideEffects(appView, code.context());
- }
-
- @Override
public int maxInValueRegister() {
return Constants.U4BIT_MAX;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index b8a5787..296b2f9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -142,20 +142,6 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- // instance-put can be dead as long as it cannot have any of the following:
- // * NoSuchFieldError (resolution failure)
- // * IncompatibleClassChangeError (static-* instruction for instance fields)
- // * IllegalAccessError (not visible from the access context)
- // * NullPointerException (null receiver)
- // * not read at all
- boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.context());
- assert appView.enableWholeProgramOptimizations() || haveSideEffects
- : "Expected instance-put instruction to have side effects in D8";
- return !haveSideEffects;
- }
-
- @Override
public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
if (!super.identicalAfterRegisterAllocation(other, allocator)) {
return false;
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 1400455..ee7d57e 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
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -32,9 +33,9 @@
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Iterator;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
@@ -45,7 +46,7 @@
protected final List<Value> inValues = new ArrayList<>();
private BasicBlock block = null;
private int number = -1;
- private LinkedHashSet<Value> debugValues = null;
+ private Set<Value> debugValues = null;
private Position position = null;
protected Instruction(Value outValue) {
@@ -154,11 +155,9 @@
public void addDebugValue(Value value) {
assert value.hasLocalInfo();
if (debugValues == null) {
- debugValues = new LinkedHashSet<>();
+ debugValues = Sets.newIdentityHashSet();
}
- if (debugValues.add(value)) {
- value.addDebugUser(this);
- }
+ debugValues.add(value);
}
public static void clearUserInfo(Instruction instruction) {
@@ -197,17 +196,19 @@
}
public void replaceDebugValue(Value oldValue, Value newValue) {
- if (debugValues.remove(oldValue)) {
- // TODO(mathiasr): Enable this assertion when BasicBlock has current position so trivial phi
- // removal can take local info into account.
- // assert newValue.getLocalInfo() == oldValue.getLocalInfo()
- // : "Replacing debug values with inconsistent locals " +
- // oldValue.getLocalInfo() + " and " + newValue.getLocalInfo() +
- // ". This is likely a code transformation bug " +
- // "that has not taken local information into account";
- if (newValue.hasLocalInfo()) {
- addDebugValue(newValue);
- }
+ assert oldValue.hasLocalInfo();
+ assert newValue.hasLocalInfo();
+ assert newValue.getLocalInfo() == oldValue.getLocalInfo()
+ : "Replacing debug values with inconsistent locals "
+ + oldValue.getLocalInfo()
+ + " and "
+ + newValue.getLocalInfo()
+ + ". This is likely a code transformation bug "
+ + "that has not taken local information into account";
+ boolean removed = debugValues.remove(oldValue);
+ assert removed;
+ if (removed && newValue.hasLocalInfo()) {
+ newValue.addDebugLocalEnd(this);
}
}
@@ -221,12 +222,6 @@
debugValues.clear();
}
- public void moveDebugValue(Value value, Instruction target) {
- assert debugValues.contains(value);
- value.replaceDebugUser(this, target);
- debugValues.remove(value);
- }
-
public void removeDebugValue(Value value) {
assert value.hasLocalInfo();
if (debugValues != null) {
@@ -591,10 +586,10 @@
}
/** Returns true is this instruction can be treated as dead code if its outputs are not used. */
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- // TODO(b/129530569): instructions with fine-grained side effect analysis may use:
- // return !instructionMayHaveSideEffects(appView, code.method.holder());
- return !instructionInstanceCanThrow();
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+ return instructionMayHaveSideEffects(appView, code.context())
+ ? DeadInstructionResult.notDead()
+ : DeadInstructionResult.deadIfOutValueIsDead();
}
/**
@@ -717,12 +712,12 @@
return null;
}
- public boolean isAssumeDynamicType() {
- return false;
+ public final boolean isAssumeWithDynamicTypeAssumption() {
+ return isAssume() && asAssume().hasDynamicTypeAssumption();
}
- public boolean isAssumeNonNull() {
- return false;
+ public final boolean isAssumeWithNonNullAssumption() {
+ return isAssume() && asAssume().hasNonNullAssumption();
}
public boolean isBinop() {
@@ -1466,17 +1461,70 @@
return false;
}
- public enum SideEffectAssumption {
- NONE,
- CLASS_ALREADY_INITIALIZED,
- RECEIVER_NOT_NULL;
+ public static class SideEffectAssumption {
- boolean canAssumeClassIsAlreadyInitialized() {
- return this == CLASS_ALREADY_INITIALIZED;
+ public static final SideEffectAssumption NONE = new SideEffectAssumption();
+
+ public static final SideEffectAssumption CLASS_ALREADY_INITIALIZED =
+ new SideEffectAssumption() {
+
+ @Override
+ public boolean canAssumeClassIsAlreadyInitialized() {
+ return true;
+ }
+ };
+
+ public static final SideEffectAssumption INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS =
+ new SideEffectAssumption() {
+
+ @Override
+ public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() {
+ return true;
+ }
+ };
+
+ public static final SideEffectAssumption RECEIVER_NOT_NULL =
+ new SideEffectAssumption() {
+
+ @Override
+ public boolean canAssumeReceiverIsNotNull() {
+ return true;
+ }
+ };
+
+ public boolean canAssumeClassIsAlreadyInitialized() {
+ return false;
}
- boolean canAssumeReceiverIsNotNull() {
- return this == RECEIVER_NOT_NULL;
+ public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() {
+ return false;
+ }
+
+ public boolean canAssumeReceiverIsNotNull() {
+ return false;
+ }
+
+ public SideEffectAssumption join(SideEffectAssumption other) {
+ return new SideEffectAssumption() {
+
+ @Override
+ public boolean canAssumeClassIsAlreadyInitialized() {
+ return SideEffectAssumption.this.canAssumeClassIsAlreadyInitialized()
+ || other.canAssumeClassIsAlreadyInitialized();
+ }
+
+ @Override
+ public boolean canAssumeInvokedMethodDoesNotHaveSideEffects() {
+ return SideEffectAssumption.this.canAssumeInvokedMethodDoesNotHaveSideEffects()
+ || other.canAssumeInvokedMethodDoesNotHaveSideEffects();
+ }
+
+ @Override
+ public boolean canAssumeReceiverIsNotNull() {
+ return SideEffectAssumption.this.canAssumeInvokedMethodDoesNotHaveSideEffects()
+ || other.canAssumeInvokedMethodDoesNotHaveSideEffects();
+ }
+ };
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index a546ee3..2790e66 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -58,10 +58,18 @@
*/
void removeOrReplaceByDebugLocalRead();
+ default boolean hasInsertionPosition() {
+ return false;
+ }
+
default void setInsertionPosition(Position position) {
// Intentionally empty.
}
+ default void unsetInsertionPosition() {
+ // Intentionally empty.
+ }
+
default Value insertConstNullInstruction(IRCode code, InternalOptions options) {
return insertConstNumberInstruction(code, options, 0, TypeElement.getNull());
}
@@ -104,20 +112,25 @@
/**
* Split the block into two blocks at the point of the {@link ListIterator} cursor. The existing
- * block will have all the instructions before the cursor, and the new block all the
- * instructions after the cursor.
+ * block will have all the instructions before the cursor, and the new block all the instructions
+ * after the cursor.
*
- * If the current block has catch handlers these catch handlers will be attached to the block
+ * <p>If the current block has catch handlers these catch handlers will be attached to the block
* containing the throwing instruction after the split.
*
* @param code the IR code for the block this iterator originates from.
* @param blockIterator basic block iterator used to iterate the blocks. This must be positioned
- * just after the block for which this is the instruction iterator. After this method returns it
- * will be positioned just after the basic block returned. Calling {@link #remove} without
- * further navigation will remove that block.
+ * just after the block for which this is the instruction iterator. After this method returns
+ * it will be positioned just after the basic block returned. Calling {@link #remove} without
+ * further navigation will remove that block.
+ * @param keepCatchHandlers whether to keep catch handlers on the original block.
* @return Returns the new block with the instructions after the cursor.
*/
- BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator);
+ BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers);
+
+ default BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) {
+ return split(code, blockIterator, hasPrevious() && peekPrevious().instructionTypeCanThrow());
+ }
default BasicBlock split(IRCode code) {
return split(code, null);
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 0f736a0..020254f 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
@@ -20,12 +20,11 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.ArrayList;
import java.util.List;
-import java.util.function.Predicate;
public class InvokeDirect extends InvokeMethodWithReceiver {
@@ -162,54 +161,20 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
ProgramMethod context = code.context();
if (instructionMayHaveSideEffects(appView, context)) {
- return false;
+ return DeadInstructionResult.notDead();
}
-
- if (appView.dexItemFactory().isConstructor(getInvokedMethod())) {
- // If it is a constructor call that initializes an uninitialized object, then the
- // uninitialized object must be dead. This is the case if all the constructor calls cannot
- // have side effects and the instance is dead except for the constructor calls.
- List<Instruction> otherInitCalls = null;
- for (Instruction user : getReceiver().uniqueUsers()) {
- if (user == this) {
- continue;
- }
- if (user.isInvokeDirect()) {
- InvokeDirect invoke = user.asInvokeDirect();
- if (appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())
- && invoke.getReceiver() == getReceiver()) {
- // If another constructor call than `this` is found, then it must not have side effects.
- if (invoke.instructionMayHaveSideEffects(appView, context)) {
- return false;
- }
- if (otherInitCalls == null) {
- otherInitCalls = new ArrayList<>();
- }
- otherInitCalls.add(invoke);
- }
- }
- }
-
- // Now check that the instance is dead except for the constructor calls.
- final List<Instruction> finalOtherInitCalls = otherInitCalls;
- Predicate<Instruction> ignoreConstructorCalls =
- instruction ->
- instruction == this
- || (finalOtherInitCalls != null && finalOtherInitCalls.contains(instruction));
- if (!getReceiver().isDead(appView, code, ignoreConstructorCalls)) {
- return false;
- }
-
- // Verify that it is not a super-constructor call (these cannot be removed).
- if (getReceiver().getAliasedValue() == code.getThis()) {
- return false;
- }
+ if (!getInvokedMethod().isInstanceInitializer(appView)) {
+ return DeadInstructionResult.deadIfOutValueIsDead();
}
-
- return true;
+ // Super-constructor calls cannot be removed.
+ if (getReceiver().getAliasedValue() == code.getThis()) {
+ return DeadInstructionResult.notDead();
+ }
+ // Constructor calls can only be removed if the receiver is dead.
+ return DeadInstructionResult.deadIfInValueIsDead(getReceiver());
}
@Override
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 a2aa502..79f7152 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
@@ -12,7 +12,7 @@
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;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -150,7 +150,8 @@
return false;
}
// Check that the receiver information comes from a dynamic type.
- if (!getReceiver().definition.isAssumeDynamicType()) {
+ if (!getReceiver()
+ .isDefinedByInstructionSatisfying(Instruction::isAssumeWithDynamicTypeAssumption)) {
return false;
}
// Now, it can be that the upper bound is more precise than the lower:
@@ -208,9 +209,12 @@
assert appView.appInfo().hasLiveness();
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- ResolutionResult resolutionResult =
- appViewWithLiveness.appInfo().resolveMethod(getInvokedMethod(), getInterfaceBit());
- if (resolutionResult.isFailedResolution()) {
+ SingleResolutionResult resolutionResult =
+ appViewWithLiveness
+ .appInfo()
+ .resolveMethod(getInvokedMethod(), getInterfaceBit())
+ .asSingleResolution();
+ if (resolutionResult == null) {
return true;
}
@@ -221,6 +225,16 @@
return true;
}
+ if (assumption.canAssumeInvokedMethodDoesNotHaveSideEffects()) {
+ return false;
+ }
+
+ DexEncodedMethod resolvedMethod = resolutionResult.getResolvedMethod();
+ if (appViewWithLiveness.appInfo().noSideEffects.containsKey(getInvokedMethod())
+ || appViewWithLiveness.appInfo().noSideEffects.containsKey(resolvedMethod.getReference())) {
+ return false;
+ }
+
// Find the target and check if the invoke may have side effects.
DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context);
if (target == null) {
@@ -242,9 +256,4 @@
return optimizationInfo.mayHaveSideEffects();
}
-
- @Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !instructionMayHaveSideEffects(appView, code.context());
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index cc83815..314317e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -182,11 +182,6 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !instructionMayHaveSideEffects(appView, code.context());
- }
-
- @Override
public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index fc6028f..2783471 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -195,11 +195,6 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !instructionMayHaveSideEffects(appView, code.context());
- }
-
- @Override
public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 7f5836a..00c88b8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -229,9 +229,4 @@
type -> appInfoWithLiveness.isSubtype(context.getHolderType(), type),
Sets.newIdentityHashSet());
}
-
- @Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !instructionMayHaveSideEffects(appView, code.context());
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index 9536919..9f4a26f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import java.util.List;
@@ -32,8 +33,8 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return false;
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index d73f8df..d851eb9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -77,8 +77,9 @@
}
@Override
- public BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) {
- return currentBlockIterator.split(code, blockIterator);
+ public BasicBlock split(
+ IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) {
+ return currentBlockIterator.split(code, blockIterator, keepCatchHandlers);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 4c87b70..dd74687 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
@@ -80,10 +81,14 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !(appView.options().debug
- || code.method().getOptimizationInfo().isReachabilitySensitive())
- && appView.options().isGeneratingDex();
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+ InternalOptions options = appView.options();
+ if (options.debug
+ || code.context().getDefinition().getOptimizationInfo().isReachabilitySensitive()
+ || options.isGeneratingClassFiles()) {
+ return DeadInstructionResult.notDead();
+ }
+ return DeadInstructionResult.deadIfOutValueIsDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c730efc..eb12b5a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -82,13 +83,16 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
if (instructionInstanceCanThrow()) {
- return false;
+ return DeadInstructionResult.notDead();
}
// This would belong better in instructionInstanceCanThrow, but that is not passed an appInfo.
DexType baseType = type.toBaseType(appView.dexItemFactory());
- return baseType.isPrimitiveType() || appView.definitionFor(baseType) != null;
+ if (baseType.isPrimitiveType() || appView.definitionFor(baseType) != null) {
+ return DeadInstructionResult.deadIfOutValueIsDead();
+ }
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index 54d1ad4..bc75936 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -138,9 +138,4 @@
public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
return false;
}
-
- @Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !instructionMayHaveSideEffects(appView, code.context());
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index c1b2bd7..81af63c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -198,11 +198,6 @@
return false;
}
- @Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !instructionMayHaveSideEffects(appView, code.context());
- }
-
public void markNoSpilling() {
allowSpilling = false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index a9365d8..cd5cb77 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -260,6 +260,18 @@
// leave the phi in there and check at the end that there are no trivial phis.
return false;
}
+ if (getLocalInfo() != same.getLocalInfo()) {
+ if (getLocalInfo() == null) {
+ // The to-be replaced phi has no local info, so all is OK.
+ } else if (same.getLocalInfo() == null) {
+ // Move the local info to the replacement phi.
+ same.setLocalInfo(getLocalInfo());
+ } else {
+ // The phi's define distinct locals and are not trivially the same.
+ assert hasLocalInfo() && same.hasLocalInfo();
+ return false;
+ }
+ }
// Ensure that the value that replaces this phi is constrained to the type of this phi.
if (builder != null && type.isPreciseType() && !type.isBottom()) {
builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(type));
@@ -430,6 +442,9 @@
}
}
}
+ if (getType().isReferenceType() && getType().isDefinitelyNotNull()) {
+ return result.asReferenceType().asMeetWithNotNull();
+ }
return result;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 0124c45..9ec7256 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -91,9 +92,9 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
// Pop cannot be dead code as it modifies the stack height.
- return false;
+ return DeadInstructionResult.notDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 4dc7c12..2a5ffbd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -149,16 +149,6 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- // static-get can be dead as long as it cannot have any of the following:
- // * NoSuchFieldError (resolution failure)
- // * IncompatibleClassChangeError (static-* instruction for instance fields)
- // * IllegalAccessError (not visible from the access context)
- // * side-effects in <clinit>
- return !instructionMayHaveSideEffects(appView, code.context());
- }
-
- @Override
public int maxInValueRegister() {
return Constants.U8BIT_MAX;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 36b6520..e268cbe 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -138,20 +138,6 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- // static-put can be dead as long as it cannot have any of the following:
- // * NoSuchFieldError (resolution failure)
- // * IncompatibleClassChangeError (static-* instruction for instance fields)
- // * IllegalAccessError (not visible from the access context)
- // * side-effects in <clinit>
- // * not read _globally_
- boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.context());
- assert appView.enableWholeProgramOptimizations() || haveSideEffects
- : "Expected static-put instruction to have side effects in D8";
- return !haveSideEffects;
- }
-
- @Override
public int maxInValueRegister() {
return Constants.U8BIT_MAX;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index ccd57ea..8100111 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
@@ -100,8 +101,11 @@
}
@Override
- public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
- return !(outValue instanceof FixedLocalValue);
+ public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+ if (outValue instanceof FixedLocalValue) {
+ return DeadInstructionResult.notDead();
+ }
+ return DeadInstructionResult.deadIfOutValueIsDead();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 53af0fc..c2a63d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -25,6 +25,7 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.regalloc.LiveIntervals;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.MethodPosition;
@@ -37,14 +38,10 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.IntList;
-import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
-import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -153,63 +150,13 @@
private static class DebugData {
final DebugLocalInfo local;
- Map<Instruction, DebugUse> users = new HashMap<>();
+ Set<Instruction> users = Sets.newIdentityHashSet();
DebugData(DebugLocalInfo local) {
this.local = local;
}
}
- // A debug-value user represents a point where the value is live, ends or starts.
- // If a point is marked as both ending and starting then it is simply live, but we maintain
- // the marker so as not to unintentionally end it if marked again.
- private enum DebugUse {
- LIVE, START, END, LIVE_FINAL;
-
- DebugUse start() {
- switch (this) {
- case LIVE:
- case START:
- return START;
- case END:
- case LIVE_FINAL:
- return LIVE_FINAL;
- default:
- throw new Unreachable();
- }
- }
-
- DebugUse end() {
- switch (this) {
- case LIVE:
- case END:
- return END;
- case START:
- case LIVE_FINAL:
- return LIVE_FINAL;
- default:
- throw new Unreachable();
- }
- }
-
- static DebugUse join(DebugUse a, DebugUse b) {
- if (a == LIVE_FINAL || b == LIVE_FINAL) {
- return LIVE_FINAL;
- }
- if (a == b) {
- return a;
- }
- if (a == LIVE) {
- return b;
- }
- if (b == LIVE) {
- return a;
- }
- assert (a == START && b == END) || (a == END && b == START);
- return LIVE_FINAL;
- }
- }
-
public static final int UNDEFINED_NUMBER = -1;
public static final Value UNDEFINED = new Value(UNDEFINED_NUMBER, TypeElement.getBottom(), null);
@@ -326,62 +273,16 @@
debugData = new DebugData(local);
}
- public void clearLocalInfo() {
- assert debugData.users.isEmpty();
- debugData = null;
- }
-
- public boolean hasSameOrNoLocal(Value other) {
- assert other != null;
- return hasLocalInfo()
- ? other.getLocalInfo() == this.getLocalInfo()
- : !other.hasLocalInfo();
- }
-
- public List<Instruction> getDebugLocalStarts() {
- if (debugData == null) {
- return Collections.emptyList();
- }
- List<Instruction> starts = new ArrayList<>(debugData.users.size());
- for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
- if (entry.getValue() == DebugUse.START) {
- starts.add(entry.getKey());
- }
- }
- return starts;
- }
-
- public List<Instruction> getDebugLocalEnds() {
- if (debugData == null) {
- return Collections.emptyList();
- }
- List<Instruction> ends = new ArrayList<>(debugData.users.size());
- for (Entry<Instruction, DebugUse> entry : debugData.users.entrySet()) {
- if (entry.getValue() == DebugUse.END) {
- ends.add(entry.getKey());
- }
- }
- return ends;
- }
-
- public void addDebugLocalStart(Instruction start) {
- assert start != null;
- debugData.users.put(start, markStart(debugData.users.get(start)));
- }
-
- private DebugUse markStart(DebugUse use) {
- assert use != null;
- return use == null ? DebugUse.START : use.start();
+ public Set<Instruction> getDebugLocalEnds() {
+ return debugData == null
+ ? Collections.emptySet()
+ : Collections.unmodifiableSet(debugData.users);
}
public void addDebugLocalEnd(Instruction end) {
assert end != null;
- debugData.users.put(end, markEnd(debugData.users.get(end)));
- }
-
- private DebugUse markEnd(DebugUse use) {
- assert use != null;
- return use == null ? DebugUse.END : use.end();
+ debugData.users.add(end);
+ end.addDebugValue(this);
}
public void linkTo(Value other) {
@@ -491,7 +392,7 @@
}
public Set<Instruction> debugUsers() {
- return debugData == null ? null : Collections.unmodifiableSet(debugData.users.keySet());
+ return debugData == null ? null : Collections.unmodifiableSet(debugData.users);
}
public boolean hasAnyUsers() {
@@ -502,6 +403,10 @@
return debugData != null && !debugData.users.isEmpty();
}
+ public boolean hasNonDebugUsers() {
+ return hasUsers() || hasPhiUsers();
+ }
+
public boolean hasPhiUsers() {
return !phiUsers.isEmpty();
}
@@ -618,11 +523,6 @@
uniquePhiUsers = null;
}
- public void addDebugUser(Instruction user) {
- assert hasLocalInfo();
- debugData.users.putIfAbsent(user, DebugUse.LIVE);
- }
-
public boolean isUninitializedLocal() {
return definition != null && definition.isDebugLocalUninitialized();
}
@@ -684,7 +584,7 @@
user.replaceOperand(this, newValue);
}
if (debugData != null) {
- for (Entry<Instruction, DebugUse> user : debugData.users.entrySet()) {
+ for (Instruction user : debugData.users) {
replaceUserInDebugData(user, newValue);
}
debugData.users.clear();
@@ -746,10 +646,10 @@
}
}
if (debugData != null) {
- Iterator<Entry<Instruction, DebugUse>> users = debugData.users.entrySet().iterator();
+ Iterator<Instruction> users = debugData.users.iterator();
while (users.hasNext()) {
- Entry<Instruction, DebugUse> user = users.next();
- if (selectedInstructions.contains(user.getKey())) {
+ Instruction user = users.next();
+ if (selectedInstructions.contains(user)) {
replaceUserInDebugData(user, newValue);
users.remove();
}
@@ -757,30 +657,18 @@
}
}
- private void replaceUserInDebugData(Entry<Instruction, DebugUse> user, Value newValue) {
- Instruction instruction = user.getKey();
- DebugUse debugUse = user.getValue();
- instruction.replaceDebugValue(this, newValue);
+ private void replaceUserInDebugData(Instruction user, Value newValue) {
+ user.replaceDebugValue(this, newValue);
// If user is a DebugLocalRead and now has no debug values, we would like to remove it.
// However, replaceUserInDebugData() is called in contexts where the instruction list is being
// iterated, so we cannot remove user from the instruction list at this point.
- if (newValue.hasLocalInfo()) {
- DebugUse existing = newValue.debugData.users.get(instruction);
- assert existing != null;
- newValue.debugData.users.put(instruction, DebugUse.join(debugUse, existing));
- }
}
public void replaceDebugUser(Instruction oldUser, Instruction newUser) {
- DebugUse use = debugData.users.remove(oldUser);
- if (use == DebugUse.START && newUser.outValue == this) {
- // Register allocation requires that debug values are live at the entry to the instruction.
- // Remove this debug use since it is starting at the instruction that defines it.
- return;
- }
- if (use != null) {
- newUser.addDebugValue(this);
- debugData.users.put(newUser, use);
+ boolean removed = debugData.users.remove(oldUser);
+ assert removed;
+ if (removed) {
+ addDebugLocalEnd(newUser);
}
}
@@ -948,8 +836,8 @@
*/
public boolean isNeverNull() {
assert type.isReferenceType();
- return (definition != null && definition.isAssumeNonNull())
- || type.nullability().isDefinitelyNotNull();
+ return isDefinedByInstructionSatisfying(Instruction::isAssumeWithNonNullAssumption)
+ || type.isDefinitelyNotNull();
}
public boolean isArgument() {
@@ -1093,9 +981,18 @@
if (ignoreUser.test(instruction)) {
continue;
}
- if (!instruction.canBeDeadCode(appView, code)) {
+ DeadInstructionResult result = instruction.canBeDeadCode(appView, code);
+ if (result.isNotDead()) {
return false;
}
+ if (result.isMaybeDead()) {
+ for (Value valueRequiredToBeDead : result.getValuesRequiredToBeDead()) {
+ if (!active.contains(valueRequiredToBeDead)
+ && !valueRequiredToBeDead.isDead(appView, code, ignoreUser, active)) {
+ return false;
+ }
+ }
+ }
Value outValue = instruction.outValue();
if (outValue != null
&& !active.contains(outValue)
@@ -1168,14 +1065,23 @@
Value root = getAliasedValue();
if (root.isPhi()) {
assert getSpecificAliasedValue(
- value -> !value.isPhi() && value.definition.isAssumeDynamicType())
+ value ->
+ value.isDefinedByInstructionSatisfying(
+ Instruction::isAssumeWithDynamicTypeAssumption))
== null;
- return root.getDynamicUpperBoundType(appView);
+ TypeElement result = root.getDynamicUpperBoundType(appView);
+ if (getType().isReferenceType() && getType().isDefinitelyNotNull()) {
+ return result.asReferenceType().asMeetWithNotNull();
+ }
+ return result;
}
// Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
Value aliasedValue =
- getSpecificAliasedValue(value -> !value.isPhi() && value.definition.isAssumeDynamicType());
+ getSpecificAliasedValue(
+ value ->
+ value.isDefinedByInstructionSatisfying(
+ Instruction::isAssumeWithDynamicTypeAssumption));
TypeElement lattice;
if (aliasedValue != null) {
// If there is an alias of the receiver, which is defined by an Assume instruction that
@@ -1228,7 +1134,8 @@
}
// Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
- Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
+ Value aliasedValue =
+ getSpecificAliasedValue(value -> value.definition.isAssumeWithDynamicTypeAssumption());
if (aliasedValue != null) {
ClassTypeElement lattice =
aliasedValue.definition.asAssume().getDynamicTypeAssumption().getDynamicLowerBoundType();
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 0dd98e2..60fcec2 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
@@ -2377,12 +2377,10 @@
// Add a use if this instruction is overwriting a previous value of the same local.
if (previousLocalValue != null && previousLocalValue.getLocalInfo() == ir.getLocalInfo()) {
assert ir.outValue() != null;
- ir.addDebugValue(previousLocalValue);
previousLocalValue.addDebugLocalEnd(ir);
}
// Add reads of locals if any are pending.
for (Value value : debugLocalEnds) {
- ir.addDebugValue(value);
value.addDebugLocalEnd(ir);
}
previousLocalValue = null;
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 844ccce..35760b4 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
@@ -53,7 +53,6 @@
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.AssumeInserter;
-import com.android.tools.r8.ir.optimize.Assumer;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -70,7 +69,6 @@
import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
-import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
import com.android.tools.r8.ir.optimize.enums.EnumValueOptimizer;
@@ -160,15 +158,13 @@
private final Devirtualizer devirtualizer;
private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
private final StringSwitchRemover stringSwitchRemover;
- private final UninstantiatedTypeOptimization uninstantiatedTypeOptimization;
private final TypeChecker typeChecker;
private final DesugaredLibraryAPIConverter desugaredLibraryAPIConverter;
private final ServiceLoaderRewriter serviceLoaderRewriter;
private final EnumValueOptimizer enumValueOptimizer;
private final EnumUnboxer enumUnboxer;
- // Assumers that will insert Assume instructions.
- public final Collection<Assumer> assumers = new ArrayList<>();
+ public final AssumeInserter assumeInserter;
private final DynamicTypeOptimization dynamicTypeOptimization;
final AssertionsRewriter assertionsRewriter;
@@ -256,7 +252,6 @@
this.lensCodeRewriter = null;
this.identifierNameStringMarker = null;
this.devirtualizer = null;
- this.uninstantiatedTypeOptimization = null;
this.typeChecker = null;
this.d8NestBasedAccessDesugaring = null;
this.stringSwitchRemover = null;
@@ -264,6 +259,7 @@
this.methodOptimizationInfoCollector = null;
this.enumValueOptimizer = null;
this.enumUnboxer = null;
+ this.assumeInserter = null;
return;
}
this.lambdaRewriter =
@@ -291,20 +287,12 @@
assert appView.appInfo().hasLiveness();
assert appView.rootSet() != null;
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- if (options.enableNonNullTracking) {
- assumers.add(new AssumeInserter(appViewWithLiveness));
- }
+ assumeInserter = new AssumeInserter(appViewWithLiveness);
this.classInliner =
options.enableClassInlining && options.enableInlining ? new ClassInliner() : null;
this.classStaticizer =
options.enableClassStaticizer ? new ClassStaticizer(appViewWithLiveness, this) : null;
- this.dynamicTypeOptimization =
- options.enableDynamicTypeOptimization
- ? new DynamicTypeOptimization(appViewWithLiveness)
- : null;
- if (dynamicTypeOptimization != null) {
- assumers.add(dynamicTypeOptimization);
- }
+ this.dynamicTypeOptimization = new DynamicTypeOptimization(appViewWithLiveness);
this.fieldAccessAnalysis =
FieldAccessAnalysis.enable(options) ? new FieldAccessAnalysis(appViewWithLiveness) : null;
this.libraryMethodOverrideAnalysis =
@@ -329,10 +317,6 @@
}
this.devirtualizer =
options.enableDevirtualization ? new Devirtualizer(appViewWithLiveness) : null;
- this.uninstantiatedTypeOptimization =
- options.enableUninstantiatedTypeOptimization
- ? new UninstantiatedTypeOptimization(appViewWithLiveness)
- : null;
this.typeChecker = new TypeChecker(appViewWithLiveness);
this.d8NestBasedAccessDesugaring = null;
this.serviceLoaderRewriter =
@@ -347,6 +331,7 @@
this.enumValueOptimizer =
options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
} else {
+ this.assumeInserter = null;
this.classInliner = null;
this.classStaticizer = null;
this.dynamicTypeOptimization = null;
@@ -359,7 +344,6 @@
this.lensCodeRewriter = null;
this.identifierNameStringMarker = null;
this.devirtualizer = null;
- this.uninstantiatedTypeOptimization = null;
this.typeChecker = null;
this.d8NestBasedAccessDesugaring =
options.shouldDesugarNests() ? new D8NestBasedAccessDesugaring(appView) : null;
@@ -489,6 +473,8 @@
public DexApplication convert(DexApplication application, ExecutorService executor)
throws ExecutionException {
removeLambdaDeserializationMethods();
+ workaroundAbstractMethodOnNonAbstractClassVerificationBug(
+ executor, OptimizationFeedbackIgnore.getInstance());
timing.begin("IR conversion");
ThreadUtils.processItems(application.classes(), this::convertMethods, executor);
@@ -652,6 +638,28 @@
}
}
+ private void workaroundAbstractMethodOnNonAbstractClassVerificationBug(
+ ExecutorService executorService, OptimizationFeedback feedback) throws ExecutionException {
+ if (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()) {
+ return;
+ }
+ assert delayedOptimizationFeedback.noUpdatesLeft();
+ ThreadUtils.processItems(
+ appView.appInfo().classes(),
+ clazz -> {
+ if (!clazz.isAbstract()) {
+ clazz.forEachMethod(
+ method -> {
+ if (method.isAbstract()) {
+ method.accessFlags.unsetAbstract();
+ finalizeEmptyThrowingCode(method, feedback);
+ }
+ });
+ }
+ },
+ executorService);
+ }
+
public DexApplication optimize() throws ExecutionException {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
@@ -667,6 +675,8 @@
computeReachabilitySensitivity(application);
collectLambdaMergingCandidates(application);
collectStaticizerCandidates(application);
+ workaroundAbstractMethodOnNonAbstractClassVerificationBug(
+ executorService, simpleOptimizationFeedback);
// The process is in two phases in general.
// 1) Subject all DexEncodedMethods to optimization, except some optimizations that require
@@ -675,6 +685,9 @@
// 2) Revisit DexEncodedMethods for the collected candidates.
printPhase("Primary optimization pass");
+ if (fieldAccessAnalysis != null) {
+ fieldAccessAnalysis.fieldAssignmentTracker().initialize();
+ }
// Process the application identifying outlining candidates.
GraphLense graphLenseForIR = appView.graphLense();
@@ -1259,9 +1272,9 @@
previous = printMethod(code, "IR after disable assertions (SSA)", previous);
- timing.begin("Insert assume instructions");
- CodeRewriter.insertAssumeInstructions(code, assumers, timing);
- timing.end();
+ if (assumeInserter != null) {
+ assumeInserter.insertAssumeInstructions(code, timing);
+ }
previous = printMethod(code, "IR after inserting assume instructions (SSA)", previous);
@@ -1323,14 +1336,6 @@
assert code.verifyTypes(appView);
- if (uninstantiatedTypeOptimization != null) {
- timing.begin("Rewrite uninstantiated types");
- uninstantiatedTypeOptimization.rewrite(code);
- timing.end();
- }
-
- assert code.verifyTypes(appView);
-
timing.begin("Remove trivial type checks/casts");
codeRewriter.removeTrivialCheckCastAndInstanceOfInstructions(code);
timing.end();
@@ -1370,10 +1375,10 @@
codeRewriter.splitRangeInvokeConstants(code);
timing.end();
timing.begin("Propogate sparse conditionals");
- new SparseConditionalConstantPropagation(code).run();
+ new SparseConditionalConstantPropagation(appView, code).run();
timing.end();
- timing.begin("Rewrite always throwing invokes");
- codeRewriter.processMethodsNeverReturningNormally(code);
+ timing.begin("Rewrite always throwing instructions");
+ codeRewriter.optimizeAlwaysThrowingInstructions(code);
timing.end();
timing.begin("Simplify control flow");
if (codeRewriter.simplifyControlFlow(code)) {
@@ -1597,7 +1602,7 @@
timing.end();
}
- if (!assumers.isEmpty()) {
+ if (assumeInserter != null) {
timing.begin("Remove assume instructions");
CodeRewriter.removeAssumeInstructions(appView, code);
timing.end();
@@ -1702,10 +1707,7 @@
private void finalizeEmptyThrowingCode(DexEncodedMethod method, OptimizationFeedback feedback) {
assert options.isGeneratingClassFiles() || options.isGeneratingDex();
- Code emptyThrowingCode =
- options.isGeneratingClassFiles()
- ? method.buildEmptyThrowingCfCode()
- : method.buildEmptyThrowingDexCode();
+ Code emptyThrowingCode = method.buildEmptyThrowingCode(options);
method.setCode(emptyThrowingCode, appView);
feedback.markProcessed(method, ConstraintWithTarget.ALWAYS);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index 7211439..e54ec6c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -44,8 +44,6 @@
void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method);
- void methodNeverReturnsNull(DexEncodedMethod method);
-
void methodNeverReturnsNormally(DexEncodedMethod method);
void markProcessed(DexEncodedMethod method, ConstraintWithTarget state);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index e66d342..ba49fc0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -84,16 +85,20 @@
}
}
} else if (instruction.isFieldInstruction()) {
- DexEncodedField encodedField =
- appView.definitionFor(instruction.asFieldInstruction().getField());
- if (encodedField != null && fieldAccessRequiresRewriting(encodedField, method)) {
+ // Since we only need to desugar accesses to private fields, and all accesses to private
+ // fields must be accessing the private field directly on its holder, we can lookup the
+ // field on the holder instead of resolving the field.
+ FieldInstruction fieldInstruction = instruction.asFieldInstruction();
+ DexClass holder = appView.definitionForHolder(fieldInstruction.getField());
+ DexEncodedField field = fieldInstruction.getField().lookupOnClass(holder);
+ if (field != null && fieldAccessRequiresRewriting(field, method)) {
if (instruction.isInstanceGet() || instruction.isStaticGet()) {
- DexMethod bridge = ensureFieldAccessBridge(encodedField, true);
+ DexMethod bridge = ensureFieldAccessBridge(field, true);
instructions.replaceCurrentInstruction(
new InvokeStatic(bridge, instruction.outValue(), instruction.inValues()));
} else {
assert instruction.isInstancePut() || instruction.isStaticPut();
- DexMethod bridge = ensureFieldAccessBridge(encodedField, false);
+ DexMethod bridge = ensureFieldAccessBridge(field, false);
instructions.replaceCurrentInstruction(
new InvokeStatic(bridge, instruction.outValue(), instruction.inValues()));
}
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 88108f9..a91c1aa 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
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClasspathClass;
@@ -529,15 +530,16 @@
DexMethod conversionMethod = createConversionMethod(argType, argType, argVivifiedType);
// The value is null only if the input is null.
Value convertedValue =
- createConversionValue(code, inValue.getType().nullability(), argVivifiedType);
+ createConversionValue(code, inValue.getType().nullability(), argVivifiedType, null);
return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
}
private Instruction createReturnConversionAndReplaceUses(
IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
DexMethod conversionMethod = createConversionMethod(returnType, returnVivifiedType, returnType);
- Value convertedValue = createConversionValue(code, Nullability.maybeNull(), returnType);
Value outValue = invokeMethod.outValue();
+ Value convertedValue =
+ createConversionValue(code, Nullability.maybeNull(), returnType, outValue.getLocalInfo());
outValue.replaceUsers(convertedValue);
// The only user of out value is now the new invoke static, so no type propagation is required.
outValue.setType(
@@ -571,8 +573,9 @@
conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName);
}
- private Value createConversionValue(IRCode code, Nullability nullability, DexType valueType) {
- return code.createValue(TypeElement.fromDexType(valueType, nullability, appView));
+ private Value createConversionValue(
+ IRCode code, Nullability nullability, DexType valueType, DebugLocalInfo localInfo) {
+ return code.createValue(TypeElement.fromDexType(valueType, nullability, appView), localInfo);
}
public boolean canConvert(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 7da225c..2586141 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -84,14 +84,15 @@
return appView.definitionFor(appView.graphLense().lookupType(type));
}
- private DexEncodedMethod definitionFor(
+ private DexEncodedMethod lookupOnHolder(
DexMethod method, DexClassAndMethod context, Invoke.Type invokeType) {
return appView.definitionFor(
appView.graphLense().lookupMethod(method, context.getReference(), invokeType).getMethod());
}
- private DexEncodedField definitionFor(DexField field) {
- return appView.definitionFor(appView.graphLense().lookupField(field));
+ private DexEncodedField lookupOnHolder(DexField field) {
+ DexField rewritten = appView.graphLense().lookupField(field);
+ return rewritten.lookupOnClass(appView.definitionForHolder(rewritten));
}
// Extract the list of types in the programClass' nest, of host hostClass
@@ -365,7 +366,7 @@
if (!method.holder.isClassType()) {
return false;
}
- DexEncodedMethod encodedMethod = definitionFor(method, context, invokeType);
+ DexEncodedMethod encodedMethod = lookupOnHolder(method, context, invokeType);
if (encodedMethod != null && invokeRequiresRewriting(encodedMethod, context)) {
ensureInvokeBridge(encodedMethod);
return true;
@@ -374,7 +375,10 @@
}
private boolean registerFieldAccess(DexField field, boolean isGet) {
- DexEncodedField encodedField = definitionFor(field);
+ // Since we only need to desugar accesses to private fields, and all accesses to private
+ // fields must be accessing the private field directly on its holder, we can lookup the field
+ // on the holder instead of resolving the field.
+ DexEncodedField encodedField = lookupOnHolder(field);
if (encodedField != null && fieldAccessRequiresRewriting(encodedField, context)) {
ensureFieldAccessBridge(encodedField, isGet);
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index 81b43ef..e4b35af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -340,7 +340,7 @@
if (method.holder() == dexItemFactory.kotlin.assertions.type) {
rewriteKotlinAssertionEnable(code, transformation, iterator, invoke);
} else {
- iterator.replaceCurrentInstruction(code.createIntConstant(0));
+ iterator.replaceCurrentInstruction(code.createIntConstant(0, current.getLocalInfo()));
}
}
} else if (current.isStaticPut()) {
@@ -355,13 +355,16 @@
if (isInitializerEnablingJavaVmAssertions
&& staticGet.getField().name == dexItemFactory.assertionsDisabled) {
iterator.replaceCurrentInstruction(
- code.createIntConstant(transformation == AssertionTransformation.DISABLE ? 1 : 0));
+ code.createIntConstant(
+ transformation == AssertionTransformation.DISABLE ? 1 : 0,
+ current.getLocalInfo()));
}
// Rewrite kotlin._Assertions.ENABLED getter.
if (staticGet.getField() == dexItemFactory.kotlin.assertions.enabledField) {
iterator.replaceCurrentInstruction(
code.createIntConstant(
- kotlinTransformation == AssertionTransformation.DISABLE ? 0 : 1));
+ kotlinTransformation == AssertionTransformation.DISABLE ? 0 : 1,
+ current.getLocalInfo()));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index 611a0a1..0008896 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -3,19 +3,23 @@
// 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.analysis.type.Nullability.definitelyNotNull;
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Assume;
+import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
@@ -29,8 +33,11 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.TriConsumer;
+import com.android.tools.r8.utils.TriFunction;
+import com.android.tools.r8.utils.TriPredicate;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
@@ -44,24 +51,23 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
-import java.util.function.BiFunction;
-import java.util.function.BiPredicate;
+import java.util.function.Consumer;
import java.util.function.Predicate;
-public class AssumeInserter implements Assumer {
+public class AssumeInserter {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private final InternalOptions options;
public AssumeInserter(AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
+ this.options = appView.options();
}
- @Override
public void insertAssumeInstructions(IRCode code, Timing timing) {
insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing);
}
- @Override
public void insertAssumeInstructionsInBlocks(
IRCode code,
BasicBlockIterator blockIterator,
@@ -89,7 +95,7 @@
timing.end();
timing.begin("Part 3: Compute dominated users");
- Map<Instruction, Set<Value>> redundantKeys =
+ Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues =
computeDominanceForAssumedValues(code, assumedValues);
timing.end();
if (assumedValues.isEmpty()) {
@@ -97,7 +103,7 @@
}
timing.begin("Part 4: Remove redundant dominated assume instructions");
- removeRedundantDominatedAssumeInstructions(assumedValues, redundantKeys);
+ removeRedundantDominatedAssumeInstructions(assumedValues, redundantAssumedValues);
timing.end();
if (assumedValues.isEmpty()) {
return;
@@ -145,29 +151,15 @@
}
}
- Value outValue = current.outValue();
if (current.isInvokeMethod()) {
- InvokeMethod invoke = current.asInvokeMethod();
- if (invoke.hasOutValue() || !invoke.getInvokedMethod().proto.parameters.isEmpty()) {
- // Case (2) and (3).
- needsAssumeInstruction |=
- computeAssumedValuesFromSingleTarget(code, invoke, assumedValuesBuilder);
- }
+ // Case (2) and (3).
+ needsAssumeInstruction |=
+ computeAssumedValuesForInvokeMethod(
+ code, current.asInvokeMethod(), assumedValuesBuilder);
} else if (current.isFieldGet()) {
// Case (4), field-get instructions that are guaranteed to read a non-null value.
- FieldInstruction fieldInstruction = current.asFieldInstruction();
- DexField field = fieldInstruction.getField();
- if (isNullableReferenceTypeWithNonDebugUsers(outValue)) {
- DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
- if (encodedField != null) {
- FieldOptimizationInfo optimizationInfo = encodedField.getOptimizationInfo();
- if (optimizationInfo.getDynamicUpperBoundType() != null
- && optimizationInfo.getDynamicUpperBoundType().isDefinitelyNotNull()) {
- assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(current, outValue);
- needsAssumeInstruction = true;
- }
- }
- }
+ needsAssumeInstruction |=
+ computeAssumedValuesForFieldGet(current.asFieldInstruction(), assumedValuesBuilder);
}
// If we need to insert an assume instruction into a block with catch handlers, we split the
@@ -202,6 +194,35 @@
}
}
+ private boolean computeAssumedValuesForInvokeMethod(
+ IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
+ if (!invoke.hasOutValue() && invoke.getInvokedMethod().proto.parameters.isEmpty()) {
+ return false;
+ }
+
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod.holder.isArrayType()
+ && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) {
+ return computeAssumedValuesFromArrayClone(invoke, assumedValuesBuilder);
+ }
+
+ return computeAssumedValuesFromSingleTarget(code, invoke, assumedValuesBuilder);
+ }
+
+ private boolean computeAssumedValuesFromArrayClone(
+ InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
+ Value outValue = invoke.outValue();
+ if (outValue == null || !outValue.hasNonDebugUsers()) {
+ return false;
+ }
+
+ TypeElement dynamicUpperBoundType =
+ TypeElement.fromDexType(invoke.getInvokedMethod().holder, definitelyNotNull(), appView);
+ assumedValuesBuilder.addAssumedValueKnownToDominateAllUsers(
+ invoke, outValue, dynamicUpperBoundType, null);
+ return true;
+ }
+
private boolean computeAssumedValuesFromSingleTarget(
IRCode code, InvokeMethod invoke, AssumedValues.Builder assumedValuesBuilder) {
DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
@@ -214,11 +235,13 @@
// Case (2), invocations that are guaranteed to return a non-null value.
Value outValue = invoke.outValue();
- if (outValue != null
- && optimizationInfo.neverReturnsNull()
- && isNullableReferenceTypeWithNonDebugUsers(outValue)) {
- assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, outValue);
- needsAssumeInstruction = true;
+ if (outValue != null && outValue.hasNonDebugUsers()) {
+ needsAssumeInstruction =
+ computeAssumedValuesForOutValue(
+ invoke,
+ optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
+ optimizationInfo.getDynamicLowerBoundType(),
+ assumedValuesBuilder);
}
// Case (3), parameters that are not null after the invocation.
@@ -239,31 +262,116 @@
return needsAssumeInstruction;
}
+ private boolean computeAssumedValuesForFieldGet(
+ FieldInstruction fieldGet, AssumedValues.Builder assumedValuesBuilder) {
+ Value outValue = fieldGet.outValue();
+ if (!outValue.hasNonDebugUsers()) {
+ return false;
+ }
+
+ DexEncodedField field = appView.appInfo().resolveField(fieldGet.getField()).getResolvedField();
+ if (field == null) {
+ return false;
+ }
+
+ FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
+ return computeAssumedValuesForOutValue(
+ fieldGet,
+ optimizationInfo.getDynamicUpperBoundTypeOrElse(outValue.getType()),
+ optimizationInfo.getDynamicLowerBoundType(),
+ assumedValuesBuilder);
+ }
+
+ private boolean computeAssumedValuesForOutValue(
+ Instruction instruction,
+ TypeElement dynamicUpperBoundType,
+ ClassTypeElement dynamicLowerBoundType,
+ AssumedValues.Builder assumedValuesBuilder) {
+ Value outValue = instruction.outValue();
+
+ // Do not insert dynamic type information if it does not refine the static type.
+ boolean isRedundant =
+ !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView)
+ && dynamicLowerBoundType == null;
+ if (isRedundant) {
+ return false;
+ }
+
+ // Do not insert dynamic type information if the dynamic type only refines the nullability.
+ if (dynamicUpperBoundType.equalUpToNullability(outValue.getType())
+ && dynamicLowerBoundType == null) {
+ assert dynamicUpperBoundType.isDefinitelyNotNull();
+ assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(instruction, outValue);
+ } else {
+ assumedValuesBuilder.addAssumedValueKnownToDominateAllUsers(
+ instruction, outValue, dynamicUpperBoundType, dynamicLowerBoundType);
+ }
+ return true;
+ }
+
private void removeRedundantAssumeInstructions(AssumedValues assumedValues) {
assumedValues.removeIf(
- (instruction, assumedValue) -> {
+ (instruction, assumedValue, assumedValueInfo) -> {
+ // Assumed values with dynamic type information are never redundant.
+ if (assumedValueInfo.hasDynamicTypeInfo()) {
+ return false;
+ }
+
+ assert assumedValueInfo.isNonNull();
+
+ // Otherwise, it is redundant if it is defined by another instruction that guarantees its
+ // non-nullness.
if (assumedValue.isPhi()) {
return false;
}
+
Instruction definition = assumedValue.definition;
- return definition != instruction && assumedValues.contains(definition, assumedValue);
+ if (definition == instruction) {
+ return false;
+ }
+
+ AssumedValueInfo otherAssumedValueInfo =
+ assumedValues.getAssumedValueInfo(definition, assumedValue);
+ if (otherAssumedValueInfo == null) {
+ return false;
+ }
+
+ if (!otherAssumedValueInfo.isNonNull()) {
+ // This is not redundant, but we can strenghten it with the dynamic type information
+ // from the other assume instruction.
+ assumedValueInfo.setDynamicTypeAssumption(
+ otherAssumedValueInfo.getDynamicTypeAssumption());
+ return false;
+ }
+
+ return true;
});
}
- private Map<Instruction, Set<Value>> computeDominanceForAssumedValues(
+ private Map<Instruction, Map<Value, AssumedValueInfo>> computeDominanceForAssumedValues(
IRCode code, AssumedValues assumedValues) {
- Map<Instruction, Set<Value>> redundantKeys = new IdentityHashMap<>();
+ Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues = new IdentityHashMap<>();
LazyDominatorTree lazyDominatorTree = new LazyDominatorTree(code);
Map<BasicBlock, Set<BasicBlock>> dominatedBlocksCache = new IdentityHashMap<>();
assumedValues.computeDominance(
- (instruction, assumedValue) -> {
- Set<Value> alreadyAssumedValues = redundantKeys.get(instruction);
- if (alreadyAssumedValues != null && alreadyAssumedValues.contains(assumedValue)) {
- // Returning redundant() will cause the entry (instruction, assumedValue) to be removed.
- return AssumedDominance.redundant();
+ (instruction, assumedValue, assumedValueInfo) -> {
+ Map<Value, AssumedValueInfo> alreadyAssumedValues =
+ redundantAssumedValues.get(instruction);
+ if (alreadyAssumedValues != null) {
+ AssumedValueInfo alreadyAssumedValueInfo = alreadyAssumedValues.get(assumedValue);
+ if (alreadyAssumedValueInfo != null) {
+ if (assumedValueInfo.isSubsumedBy(alreadyAssumedValueInfo)) {
+ // Returning redundant() will cause the entry (instruction, assumedValue) to be
+ // removed.
+ return AssumedDominance.redundant();
+ }
+
+ // This assume value is dominated by the other assume value, so strengthen this one.
+ assumedValueInfo.strengthenWith(alreadyAssumedValueInfo);
+ }
}
- // If this value is non-null since its definition, then it is known to dominate all users.
+ // If this value is the out-value of some instruction it is known to dominate all users.
if (assumedValue == instruction.outValue()) {
return AssumedDominance.everything();
}
@@ -327,9 +435,9 @@
// Record that there is no need to insert an assume instruction for the non-null-value
// after the given user in case the user is also a null check for the non-null-value.
- redundantKeys
- .computeIfAbsent(user, ignore -> Sets.newIdentityHashSet())
- .add(assumedValue);
+ redundantAssumedValues
+ .computeIfAbsent(user, ignore -> new IdentityHashMap<>())
+ .put(assumedValue, assumedValueInfo);
}
}
for (Phi user : assumedValue.uniquePhiUsers()) {
@@ -341,68 +449,54 @@
}
return dominance.build();
});
- return redundantKeys;
+ return redundantAssumedValues;
}
private void removeRedundantDominatedAssumeInstructions(
- AssumedValues assumedValues, Map<Instruction, Set<Value>> redundantKeys) {
- assumedValues.removeAll(redundantKeys);
+ AssumedValues assumedValues,
+ Map<Instruction, Map<Value, AssumedValueInfo>> redundantAssumedValues) {
+ assumedValues.removeAll(redundantAssumedValues);
}
private void materializeAssumeInstructions(IRCode code, AssumedValues assumedValues) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions =
new IdentityHashMap<>();
- assumedValues.forEach(
- (instruction, assumedValue, assumedValueInfo) -> {
- BasicBlock block = instruction.getBlock();
- BasicBlock insertionBlock = getInsertionBlock(instruction);
- AssumedDominance dominance = assumedValueInfo.getDominance();
- Value newValue =
- code.createValue(
- assumedValue.getType().asReferenceType().asMeetWithNotNull(),
- assumedValue.getLocalInfo());
- if (dominance.isEverything()) {
- assumedValue.replaceUsers(newValue);
- } else if (dominance.isEverythingElse()) {
- assumedValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction);
- assumedValue.replacePhiUsers(newValue);
- } else if (dominance.isSomething()) {
- SomethingAssumedDominance somethingDominance = dominance.asSomething();
- somethingDominance
- .getDominatedPhiUsers()
- .forEach(
- (user, indices) -> {
- IntListIterator iterator = indices.iterator();
- while (iterator.hasNext()) {
- Value operand = user.getOperand(iterator.nextInt());
- if (operand != assumedValue) {
- assert operand.isDefinedByInstructionSatisfying(
- Instruction::isAssumeNonNull);
- iterator.remove();
- }
- }
- });
- assumedValue.replaceSelectiveUsers(
- newValue,
- somethingDominance.getDominatedUsers(),
- somethingDominance.getDominatedPhiUsers());
- }
- affectedValues.addAll(newValue.affectedValues());
-
- Assume assumeInstruction =
- Assume.createAssumeNonNullInstruction(newValue, assumedValue, instruction, appView);
- assumeInstruction.setPosition(instruction.getPosition());
- if (insertionBlock != block) {
- insertionBlock.listIterator(code).add(assumeInstruction);
- } else {
- pendingInsertions
- .computeIfAbsent(block, ignore -> new IdentityHashMap<>())
- .computeIfAbsent(instruction, ignore -> new ArrayList<>())
- .add(assumeInstruction);
- }
- });
+ // We materialize the assume instructions in two steps. First, we materialize all the assume
+ // instructions that do not dominate everything. These assume instructions can refine previous
+ // assume instructions, so we materialize those first as they are "stronger".
+ //
+ // Example:
+ // 1. Object value = getNullableValueWithDynamicType();
+ // 2. Object nullableValueWithDynamicType = assume(value, ...)
+ // 3. checkNotNull(value);
+ // 4. Object nonNullValueWithDynamicType = assume(value, ...)
+ // 5. return value;
+ //
+ // In this example, we first materialize the assume instruction in line 4, and replace the
+ // dominated use of `value` in line 5 by the new assumed value `nonNullValueWithDynamicType`.
+ // Afterwards, we materialize the assume instruction in line 2, and replace all remaining users
+ // of `value` by `nullableValueWithDynamicType`.
+ //
+ // Result:
+ // 1. Object value = getNullableValueWithDynamicType();
+ // 2. Object nullableValueWithDynamicType = assume(value, ...)
+ // 3. checkNotNull(nullableValueWithDynamicType);
+ // 4. Object nonNullValueWithDynamicType = assume(value, ...)
+ // 5. return nonNullValueWithDynamicType;
+ materializeSelectedAssumeInstructions(
+ code,
+ assumedValues,
+ affectedValues,
+ pendingInsertions,
+ assumedValueInfo -> !assumedValueInfo.dominance.isEverything());
+ materializeSelectedAssumeInstructions(
+ code,
+ assumedValues,
+ affectedValues,
+ pendingInsertions,
+ assumedValueInfo -> assumedValueInfo.dominance.isEverything());
pendingInsertions.forEach(
(block, pendingInsertionsPerInstruction) -> {
InstructionListIterator instructionIterator = block.listIterator(code);
@@ -420,6 +514,83 @@
}
}
+ private void materializeSelectedAssumeInstructions(
+ IRCode code,
+ AssumedValues assumedValues,
+ Set<Value> affectedValues,
+ Map<BasicBlock, Map<Instruction, List<Instruction>>> pendingInsertions,
+ Predicate<AssumedValueInfo> predicate) {
+ assumedValues.removeIf(
+ (instruction, assumedValue, assumedValueInfo) -> {
+ if (!predicate.test(assumedValueInfo)) {
+ return false;
+ }
+
+ BasicBlock block = instruction.getBlock();
+ BasicBlock insertionBlock = getInsertionBlock(instruction);
+
+ AssumedDominance dominance = assumedValueInfo.getDominance();
+ Value newValue =
+ assumedValueInfo.isNull()
+ ? code.createValue(TypeElement.getNull())
+ : code.createValue(
+ assumedValueInfo.isNonNull()
+ ? assumedValue.getType().asReferenceType().asMeetWithNotNull()
+ : assumedValue.getType(),
+ assumedValue.getLocalInfo());
+ if (dominance.isEverything()) {
+ assumedValue.replaceUsers(newValue);
+ } else if (dominance.isEverythingElse()) {
+ assumedValue.replaceSelectiveInstructionUsers(newValue, user -> user != instruction);
+ assumedValue.replacePhiUsers(newValue);
+ } else if (dominance.isSomething()) {
+ SomethingAssumedDominance somethingDominance = dominance.asSomething();
+ somethingDominance
+ .getDominatedPhiUsers()
+ .forEach(
+ (user, indices) -> {
+ IntListIterator iterator = indices.iterator();
+ while (iterator.hasNext()) {
+ Value operand = user.getOperand(iterator.nextInt());
+ if (operand != assumedValue) {
+ assert operand.isDefinedByInstructionSatisfying(Instruction::isAssume);
+ iterator.remove();
+ }
+ }
+ });
+ assumedValue.replaceSelectiveUsers(
+ newValue,
+ somethingDominance.getDominatedUsers(),
+ somethingDominance.getDominatedPhiUsers());
+ }
+ affectedValues.addAll(newValue.affectedValues());
+
+ Instruction assumeInstruction;
+ if (assumedValueInfo.isNull()) {
+ assumeInstruction = new ConstNumber(newValue, 0);
+ } else {
+ assumeInstruction =
+ new Assume(
+ assumedValueInfo.dynamicTypeAssumption,
+ assumedValueInfo.nonNullAssumption,
+ newValue,
+ assumedValue,
+ instruction,
+ appView);
+ }
+ assumeInstruction.setPosition(instruction.getPosition());
+ if (insertionBlock != block) {
+ insertionBlock.listIterator(code).add(assumeInstruction);
+ } else {
+ pendingInsertions
+ .computeIfAbsent(block, ignore -> new IdentityHashMap<>())
+ .computeIfAbsent(instruction, ignore -> new ArrayList<>())
+ .add(assumeInstruction);
+ }
+ return true;
+ });
+ }
+
private BasicBlock getInsertionBlock(Instruction instruction) {
if (instruction.isIf()) {
return instruction.asIf().targetFromNonNullObject();
@@ -461,10 +632,6 @@
return type.isReferenceType() && type.asReferenceType().isNullable();
}
- private static boolean isNullableReferenceTypeWithNonDebugUsers(Value value) {
- return isNullableReferenceType(value) && value.numberOfAllNonDebugUsers() > 0;
- }
-
private static boolean isNullableReferenceTypeWithOtherNonDebugUsers(
Value value, Instruction ignore) {
if (isNullableReferenceType(value)) {
@@ -483,6 +650,7 @@
static class AssumedValueInfo {
AssumedDominance dominance;
+ DynamicTypeAssumption dynamicTypeAssumption;
NonNullAssumption nonNullAssumption;
AssumedValueInfo(AssumedDominance dominance) {
@@ -497,9 +665,55 @@
this.dominance = dominance;
}
+ boolean hasDynamicTypeInfo() {
+ return dynamicTypeAssumption != null;
+ }
+
+ DynamicTypeAssumption getDynamicTypeAssumption() {
+ return dynamicTypeAssumption;
+ }
+
+ void setDynamicTypeAssumption(DynamicTypeAssumption dynamicTypeAssumption) {
+ this.dynamicTypeAssumption = dynamicTypeAssumption;
+ }
+
+ void setDynamicTypeAssumption(
+ TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
+ dynamicTypeAssumption =
+ new DynamicTypeAssumption(dynamicUpperBoundType, dynamicLowerBoundType);
+ if (dynamicUpperBoundType.isDefinitelyNotNull()) {
+ setNotNull();
+ }
+ if (dynamicLowerBoundType != null && dynamicLowerBoundType.isDefinitelyNotNull()) {
+ setNotNull();
+ }
+ }
+
+ boolean isNull() {
+ return dynamicTypeAssumption != null
+ && dynamicTypeAssumption.getDynamicUpperBoundType().isDefinitelyNull();
+ }
+
+ boolean isNonNull() {
+ return nonNullAssumption != null;
+ }
+
void setNotNull() {
nonNullAssumption = NonNullAssumption.get();
}
+
+ boolean isSubsumedBy(AssumedValueInfo other) {
+ return !hasDynamicTypeInfo() && other.isNonNull();
+ }
+
+ void strengthenWith(AssumedValueInfo info) {
+ if (info.isNonNull()) {
+ setNotNull();
+ }
+ if (!hasDynamicTypeInfo() && info.hasDynamicTypeInfo()) {
+ setDynamicTypeAssumption(info.getDynamicTypeAssumption());
+ }
+ }
}
static class AssumedValues {
@@ -519,7 +733,8 @@
return new Builder();
}
- void computeDominance(BiFunction<Instruction, Value, AssumedDominance> function) {
+ void computeDominance(
+ TriFunction<Instruction, Value, AssumedValueInfo, AssumedDominance> function) {
Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator =
assumedValues.entrySet().iterator();
while (outerIterator.hasNext()) {
@@ -539,7 +754,7 @@
continue;
}
assert dominance.isUnknown();
- dominance = function.apply(instruction, assumedValue);
+ dominance = function.apply(instruction, assumedValue, assumedValueInfo);
if ((dominance.isNothing() && !assumedValue.isArgument()) || dominance.isUnknown()) {
innerIterator.remove();
} else {
@@ -552,9 +767,9 @@
}
}
- boolean contains(Instruction instruction, Value assumedValue) {
+ AssumedValueInfo getAssumedValueInfo(Instruction instruction, Value assumedValue) {
Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction);
- return dominancePerValue != null && dominancePerValue.containsKey(assumedValue);
+ return dominancePerValue != null ? dominancePerValue.get(assumedValue) : null;
}
boolean isEmpty() {
@@ -569,12 +784,12 @@
consumer.accept(instruction, assumedValue, assumedValueInfo)));
}
- void removeAll(Map<Instruction, Set<Value>> keys) {
+ void removeAll(Map<Instruction, Map<Value, AssumedValueInfo>> keys) {
keys.forEach(
- (instruction, values) -> {
+ (instruction, redundantAssumedValues) -> {
Map<Value, AssumedValueInfo> dominancePerValue = assumedValues.get(instruction);
if (dominancePerValue != null) {
- values.forEach(dominancePerValue::remove);
+ redundantAssumedValues.keySet().forEach(dominancePerValue::remove);
if (dominancePerValue.isEmpty()) {
assumedValues.remove(instruction);
}
@@ -582,7 +797,7 @@
});
}
- void removeIf(BiPredicate<Instruction, Value> predicate) {
+ void removeIf(TriPredicate<Instruction, Value, AssumedValueInfo> predicate) {
Iterator<Entry<Instruction, Map<Value, AssumedValueInfo>>> outerIterator =
assumedValues.entrySet().iterator();
while (outerIterator.hasNext()) {
@@ -592,8 +807,10 @@
Iterator<Entry<Value, AssumedValueInfo>> innerIterator =
dominancePerValue.entrySet().iterator();
while (innerIterator.hasNext()) {
- Value assumedValue = innerIterator.next().getKey();
- if (predicate.test(instruction, assumedValue)) {
+ Entry<Value, AssumedValueInfo> innerEntry = innerIterator.next();
+ Value assumedValue = innerEntry.getKey();
+ AssumedValueInfo assumedValueInfo = innerEntry.getValue();
+ if (predicate.test(instruction, assumedValue, assumedValueInfo)) {
innerIterator.remove();
}
}
@@ -609,29 +826,49 @@
new LinkedHashMap<>();
// Used to avoid unnecessary block splitting during phase 1.
- private final Set<Value> assumedValuesKnownToDominateAllUsers = Sets.newIdentityHashSet();
+ private final Set<Value> nonNullValuesKnownToDominateAllUsers = Sets.newIdentityHashSet();
- private void addNonNullValue(
- Instruction instruction, Value nonNullValue, AssumedDominance dominance) {
- assumedValues
- .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>())
- .computeIfAbsent(nonNullValue, ignore -> new AssumedValueInfo(dominance))
- .setNotNull();
- if (dominance.isEverything()) {
- assumedValuesKnownToDominateAllUsers.add(nonNullValue);
+ private void updateAssumedValueInfo(
+ Instruction instruction,
+ Value assumedValue,
+ AssumedDominance dominance,
+ Consumer<AssumedValueInfo> consumer) {
+ AssumedValueInfo assumedValueInfo =
+ assumedValues
+ .computeIfAbsent(instruction, ignore -> new LinkedHashMap<>())
+ .computeIfAbsent(assumedValue, ignore -> new AssumedValueInfo(dominance));
+ consumer.accept(assumedValueInfo);
+ if (dominance.isEverything() && assumedValueInfo.isNonNull()) {
+ nonNullValuesKnownToDominateAllUsers.add(assumedValue);
}
}
+ void addAssumedValueKnownToDominateAllUsers(
+ Instruction instruction,
+ Value assumedValue,
+ TypeElement dynamicUpperBoundType,
+ ClassTypeElement dynamicLowerBoundType) {
+ updateAssumedValueInfo(
+ instruction,
+ assumedValue,
+ AssumedDominance.everything(),
+ assumedValueInfo ->
+ assumedValueInfo.setDynamicTypeAssumption(
+ dynamicUpperBoundType, dynamicLowerBoundType));
+ }
+
void addNonNullValueKnownToDominateAllUsers(Instruction instruction, Value nonNullValue) {
- addNonNullValue(instruction, nonNullValue, AssumedDominance.everything());
+ updateAssumedValueInfo(
+ instruction, nonNullValue, AssumedDominance.everything(), AssumedValueInfo::setNotNull);
}
void addNonNullValueWithUnknownDominance(Instruction instruction, Value nonNullValue) {
- addNonNullValue(instruction, nonNullValue, AssumedDominance.unknown());
+ updateAssumedValueInfo(
+ instruction, nonNullValue, AssumedDominance.unknown(), AssumedValueInfo::setNotNull);
}
public boolean isMaybeNull(Value value) {
- return !assumedValuesKnownToDominateAllUsers.contains(value);
+ return !nonNullValuesKnownToDominateAllUsers.contains(value);
}
public AssumedValues build() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
similarity index 60%
rename from src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
rename to src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
index 7f45db3..41f72cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeRemover.java
@@ -16,60 +16,68 @@
import java.util.Set;
/**
- * When we have Assume instructions with a DynamicTypeAssumption we generally verify that the
- * dynamic type in the Assume node is always at least as precise as the static type of the
- * corresponding value.
+ * When we have Assume instructions we generally verify that the Assume instructions contribute with
+ * non-trivial information to the IR (e.g., the dynamic type should be more precise than the static
+ * type).
*
* <p>Therefore, when this property may no longer hold for an Assume instruction, we need to remove
* it.
*
* <p>This class is a helper class to remove these instructions. Unlike {@link
* CodeRewriter#removeAssumeInstructions} this class does not unconditionally remove all Assume
- * instructions, not does it remove all Assume instructions with a DynamicTypeAssumption.
+ * instructions.
*/
-public class AssumeDynamicTypeRemover {
+public class AssumeRemover {
private final AppView<?> appView;
private final IRCode code;
private final Set<Value> affectedValues = Sets.newIdentityHashSet();
- private final Set<Assume> assumeDynamicTypeInstructionsToRemove = Sets.newIdentityHashSet();
+ private final Set<Assume> assumeInstructionsToRemove = Sets.newIdentityHashSet();
private boolean mayHaveIntroducedTrivialPhi = false;
- public AssumeDynamicTypeRemover(AppView<?> appView, IRCode code) {
+ public AssumeRemover(AppView<?> appView, IRCode code) {
this.appView = appView;
this.code = code;
}
+ public Set<Value> getAffectedValues() {
+ return affectedValues;
+ }
+
public boolean mayHaveIntroducedTrivialPhi() {
return mayHaveIntroducedTrivialPhi;
}
- public void markForRemoval(Assume assumeDynamicTypeInstruction) {
- assumeDynamicTypeInstructionsToRemove.add(assumeDynamicTypeInstruction);
- }
-
- public void markUsersForRemoval(Value value) {
+ public void markAssumeDynamicTypeUsersForRemoval(Value value) {
for (Instruction user : value.aliasedUsers()) {
- if (user.isAssumeDynamicType()) {
- markForRemoval(user.asAssume());
+ if (user.isAssume()) {
+ Assume assumeInstruction = user.asAssume();
+ assumeInstruction.unsetDynamicTypeAssumption();
+ if (!assumeInstruction.hasNonNullAssumption()) {
+ assumeInstruction.unsetDynamicTypeAssumption();
+ }
}
}
}
+ private void markForRemoval(Assume assumeInstruction) {
+ assumeInstructionsToRemove.add(assumeInstruction);
+ }
+
public void removeIfMarked(
- Assume assumeDynamicTypeInstruction, InstructionListIterator instructionIterator) {
- if (assumeDynamicTypeInstructionsToRemove.remove(assumeDynamicTypeInstruction)) {
- Value inValue = assumeDynamicTypeInstruction.src();
- Value outValue = assumeDynamicTypeInstruction.outValue();
+ Assume assumeInstruction, InstructionListIterator instructionIterator) {
+ if (assumeInstructionsToRemove.remove(assumeInstruction)) {
+ Value inValue = assumeInstruction.src();
+ Value outValue = assumeInstruction.outValue();
// Check if we need to run the type analysis for the affected values of the out-value.
if (!outValue.getType().equals(inValue.getType())) {
affectedValues.addAll(outValue.affectedValues());
}
- if (outValue.numberOfPhiUsers() > 0) {
+ if (outValue.hasPhiUsers()) {
mayHaveIntroducedTrivialPhi = true;
}
@@ -78,16 +86,20 @@
}
}
- public AssumeDynamicTypeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
- if (!assumeDynamicTypeInstructionsToRemove.isEmpty()) {
+ public AssumeRemover removeMarkedInstructions() {
+ return removeMarkedInstructions(null);
+ }
+
+ public AssumeRemover removeMarkedInstructions(Set<BasicBlock> blocksToBeRemoved) {
+ if (!assumeInstructionsToRemove.isEmpty()) {
for (BasicBlock block : code.blocks) {
- if (blocksToBeRemoved.contains(block)) {
+ if (blocksToBeRemoved != null && blocksToBeRemoved.contains(block)) {
continue;
}
InstructionListIterator instructionIterator = block.listIterator(code);
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
- if (instruction.isAssumeDynamicType()) {
+ if (instruction.isAssume()) {
removeIfMarked(instruction.asAssume(), instructionIterator);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 9708120..5eb23bb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.ForEachable;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
import com.android.tools.r8.utils.LazyBox;
import com.android.tools.r8.utils.Timing;
@@ -276,7 +277,13 @@
}
private void abandonCallSitePropagation(ForEachable<ProgramMethod> methods) {
- methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo());
+ if (InternalOptions.assertionsEnabled()) {
+ synchronized (this) {
+ methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo());
+ }
+ } else {
+ methods.forEach(method -> method.getDefinition().abandonCallSiteOptimizationInfo());
+ }
}
private CallSiteOptimizationInfo computeCallSiteOptimizationInfoFromArguments(
@@ -435,12 +442,19 @@
return null;
}
- private boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned(
+ private synchronized boolean verifyAllProgramDispatchTargetsHaveBeenAbandoned(
InvokeMethod invoke, ProgramMethod context) {
ProgramMethodSet targets = invoke.lookupProgramDispatchTargets(appView, context);
if (targets != null) {
for (ProgramMethod target : targets) {
- assert target.getDefinition().getCallSiteOptimizationInfo().isAbandoned();
+ assert target.getDefinition().getCallSiteOptimizationInfo().isAbandoned()
+ : "Expected method `"
+ + target.toSourceString()
+ + "` to be marked as abandoned (called from `"
+ + invoke.toString()
+ + "` in `"
+ + context.toSourceString()
+ + "`)";
}
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index e769dc9..7272948 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -30,7 +30,6 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -361,8 +360,6 @@
private Map<DexEncodedField, StaticPut> findFinalFieldPutsWhileCollectingUnnecessaryStaticPuts(
IRCode code, ProgramMethod context, Set<StaticPut> unnecessaryStaticPuts) {
- ValueMayDependOnEnvironmentAnalysis environmentAnalysis =
- new ValueMayDependOnEnvironmentAnalysis(appView, code);
Map<DexEncodedField, StaticPut> finalFieldPuts = Maps.newIdentityHashMap();
Map<DexField, Set<StaticPut>> isWrittenBefore = Maps.newIdentityHashMap();
Set<DexEncodedField> isReadBefore = Sets.newIdentityHashSet();
@@ -471,12 +468,7 @@
// the value of one of the fields in the enclosing class.
if (instruction.isInvoke() && instruction.hasOutValue()) {
Value outValue = instruction.outValue();
- if (outValue.numberOfAllUsers() > 0) {
- if (instruction.isInvokeNewArray()
- && environmentAnalysis.isConstantArrayThroughoutMethod(outValue)) {
- // OK, this value is technically a constant.
- continue;
- }
+ if (outValue.hasNonDebugUsers()) {
return validateFinalFieldPuts(finalFieldPuts, isWrittenBefore);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 172a6b3..c6447c1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
@@ -52,8 +51,10 @@
import com.android.tools.r8.ir.code.IRMetadata;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.If.Type;
+import com.android.tools.r8.ir.code.InstanceFieldInstruction;
import com.android.tools.r8.ir.code.InstanceOf;
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.InstructionListIterator;
import com.android.tools.r8.ir.code.InstructionOrPhi;
@@ -61,6 +62,7 @@
import com.android.tools.r8.ir.code.Invoke;
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.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -79,13 +81,14 @@
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.controlflow.SwitchCaseAnalyzer;
+import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOutputMode;
import com.android.tools.r8.utils.LongInterval;
import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Suppliers;
@@ -117,6 +120,7 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.BitSet;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
@@ -159,21 +163,6 @@
this.dexItemFactory = appView.dexItemFactory();
}
- public static void insertAssumeInstructions(
- IRCode code, Collection<Assumer> assumers, Timing timing) {
- insertAssumeInstructionsInBlocks(code, assumers, alwaysTrue(), timing);
- }
-
- public static void insertAssumeInstructionsInBlocks(
- IRCode code, Collection<Assumer> assumers, Predicate<BasicBlock> blockTester, Timing timing) {
- timing.begin("Insert assume instructions");
- for (Assumer assumer : assumers) {
- assumer.insertAssumeInstructionsInBlocks(code, code.listIterator(), blockTester, timing);
- assert code.isConsistentSSA();
- }
- timing.end();
- }
-
public static void removeAssumeInstructions(AppView<?> appView, IRCode code) {
// We need to update the types of all values whose definitions depend on a non-null value.
// This is needed to preserve soundness of the types after the Assume instructions have been
@@ -1249,7 +1238,7 @@
return false;
}
- AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
+ AssumeRemover assumeRemover = new AssumeRemover(appView, code);
boolean changed = false;
boolean mayHaveRemovedTrivialPhi = false;
Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -1282,7 +1271,7 @@
// return false unless it is object.
if (argument.getType().lessThanOrEqual(outValue.getType(), appView)) {
affectedValues.addAll(outValue.affectedValues());
- assumeDynamicTypeRemover.markUsersForRemoval(outValue);
+ assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
outValue.replaceUsers(argument);
invoke.setOutValue(null);
@@ -1292,12 +1281,12 @@
}
}
}
- assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
+ assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
if (!blocksToBeRemoved.isEmpty()) {
code.removeBlocks(blocksToBeRemoved);
code.removeAllDeadAndTrivialPhis(affectedValues);
assert code.getUnreachableBlocks().isEmpty();
- } else if (mayHaveRemovedTrivialPhi || assumeDynamicTypeRemover.mayHaveIntroducedTrivialPhi()) {
+ } else if (mayHaveRemovedTrivialPhi || assumeRemover.mayHaveIntroducedTrivialPhi()) {
code.removeAllDeadAndTrivialPhis(affectedValues);
}
if (!affectedValues.isEmpty()) {
@@ -1502,7 +1491,9 @@
if (result == InstanceOfResult.UNKNOWN) {
Value aliasedValue =
inValue.getSpecificAliasedValue(
- value -> !value.isPhi() && value.definition.isAssumeDynamicType());
+ value ->
+ value.isDefinedByInstructionSatisfying(
+ Instruction::isAssumeWithDynamicTypeAssumption));
if (aliasedValue != null) {
TypeElement dynamicType =
aliasedValue
@@ -2232,7 +2223,6 @@
instruction.outValue().replaceUsers(inValue);
Value overwrittenLocal = instruction.removeDebugValue(localInfo);
if (overwrittenLocal != null) {
- inValue.definition.addDebugValue(overwrittenLocal);
overwrittenLocal.addDebugLocalEnd(inValue.definition);
}
if (prevInstruction != null &&
@@ -2553,10 +2543,13 @@
if (singleFieldValue.getField() == otherSingleFieldValue.getField()) {
simplifyIfWithKnownCondition(code, block, theIf, 0);
} else {
- DexEncodedField field = appView.definitionFor(singleFieldValue.getField());
+ DexClass holder = appView.definitionForHolder(singleFieldValue.getField());
+ DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder);
if (field != null && field.isEnum()) {
+ DexClass otherHolder =
+ appView.definitionForHolder(otherSingleFieldValue.getField());
DexEncodedField otherField =
- appView.definitionFor(otherSingleFieldValue.getField());
+ otherSingleFieldValue.getField().lookupOnClass(otherHolder);
if (otherField != null && otherField.isEnum()) {
simplifyIfWithKnownCondition(code, block, theIf, 1);
}
@@ -2822,69 +2815,94 @@
return changed;
}
- // Find all method invocations that never returns normally, split the block
- // after each such invoke instruction and follow it with a block throwing a
- // null value (which should result in NPE). Note that this throw is not
+ // Find all instructions that always throw, split the block after each such instruction and follow
+ // it with a block throwing a null value (which should result in NPE). Note that this throw is not
// expected to be ever reached, but is intended to satisfy verifier.
- public void processMethodsNeverReturningNormally(IRCode code) {
+ public void optimizeAlwaysThrowingInstructions(IRCode code) {
if (!appView.appInfo().hasLiveness()) {
return;
}
+ AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
+ ProgramMethod context = code.context();
while (blockIterator.hasNext()) {
BasicBlock block = blockIterator.next();
if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
continue;
}
- InstructionListIterator insnIterator = block.listIterator(code);
- while (insnIterator.hasNext()) {
- Instruction insn = insnIterator.next();
- if (!insn.isInvokeMethod()) {
+ if (blocksToRemove.contains(block)) {
+ continue;
+ }
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (instruction.throwsOnNullInput()) {
+ Value inValue = instruction.getNonNullInput();
+ if (inValue.isAlwaysNull(appView)) {
+ // Insert `throw null` after the instruction if it is not guaranteed to throw an NPE.
+ if (instruction.isInstanceFieldInstruction()) {
+ InstanceFieldInstruction instanceFieldInstruction =
+ instruction.asInstanceFieldInstruction();
+ if (instanceFieldInstruction.instructionInstanceCanThrow(
+ appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) {
+ instructionIterator.next();
+ }
+ } else if (instruction.isInvokeMethodWithReceiver()) {
+ InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+ SideEffectAssumption assumption =
+ SideEffectAssumption.RECEIVER_NOT_NULL.join(
+ SideEffectAssumption.INVOKED_METHOD_DOES_NOT_HAVE_SIDE_EFFECTS);
+ if (invoke.instructionMayHaveSideEffects(appView, context, assumption)) {
+ instructionIterator.next();
+ }
+ }
+ instructionIterator.replaceCurrentInstructionWithThrowNull(
+ appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues);
+ continue;
+ }
+ }
+
+ if (!instruction.isInvokeMethod()) {
continue;
}
- InvokeMethod invoke = insn.asInvokeMethod();
+ InvokeMethod invoke = instruction.asInvokeMethod();
DexEncodedMethod singleTarget =
invoke.lookupSingleTarget(appView.withLiveness(), code.context());
- if (singleTarget == null || !singleTarget.getOptimizationInfo().neverReturnsNormally()) {
+ if (singleTarget == null) {
continue;
}
- // Split the block.
- {
- BasicBlock newBlock = insnIterator.split(code, blockIterator);
- assert !insnIterator.hasNext(); // must be pointing *after* inserted GoTo.
- // Move block iterator back so current block is 'newBlock'.
- blockIterator.previous();
+ MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
- newBlock.unlinkSinglePredecessorSiblingsAllowed();
+ // If the invoke instruction is a null check, we can remove it.
+ boolean isNullCheck = false;
+ if (optimizationInfo.hasNonNullParamOrThrow()) {
+ BitSet nonNullParamOrThrow = optimizationInfo.getNonNullParamOrThrow();
+ for (int i = 0; i < invoke.arguments().size(); i++) {
+ Value argument = invoke.arguments().get(i);
+ if (argument.isAlwaysNull(appView) && nonNullParamOrThrow.get(i)) {
+ isNullCheck = true;
+ break;
+ }
+ }
}
-
- // We want to follow the invoke instruction with 'throw null', which should
- // be unreachable but is needed to satisfy the verifier. Note that we have
- // to put 'throw null' into a separate block to make sure we don't get two
- // throwing instructions in the block having catch handler. This new block
- // does not need catch handlers.
- Instruction gotoInsn = insnIterator.previous();
- assert gotoInsn.isGoto();
- assert insnIterator.hasNext();
- BasicBlock throwNullBlock = insnIterator.split(code, blockIterator);
- InstructionListIterator throwNullInsnIterator = throwNullBlock.listIterator(code);
-
- // Insert 'null' constant.
- ConstNumber nullConstant = code.createConstNull(gotoInsn.getLocalInfo());
- nullConstant.setPosition(invoke.getPosition());
- throwNullInsnIterator.add(nullConstant);
-
- // Replace Goto with Throw.
- Throw notReachableThrow = new Throw(nullConstant.outValue());
- Instruction insnGoto = throwNullInsnIterator.next();
- assert insnGoto.isGoto();
- throwNullInsnIterator.replaceCurrentInstruction(notReachableThrow);
+ // If the invoke instruction never returns normally, we can insert a throw null instruction
+ // after the invoke.
+ if (isNullCheck || optimizationInfo.neverReturnsNormally()) {
+ instructionIterator.setInsertionPosition(invoke.getPosition());
+ instructionIterator.next();
+ instructionIterator.replaceCurrentInstructionWithThrowNull(
+ appViewWithLiveness, code, blockIterator, blocksToRemove, affectedValues);
+ instructionIterator.unsetInsertionPosition();
+ }
}
}
- Set<Value> affectedValues = code.removeUnreachableBlocks();
+ code.removeBlocks(blocksToRemove);
+ assert code.getUnreachableBlocks().isEmpty();
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index ef9c66c..71684b1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
@@ -19,6 +20,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
@@ -68,7 +70,7 @@
|| !instruction.hasOutValue()
|| instruction.outValue().hasAnyUsers();
// No dead instructions.
- assert !instruction.canBeDeadCode(appView, code)
+ assert !instruction.canBeDeadCode(appView, code).isDeadIfOutValueIsDead()
|| (instruction.hasOutValue() && !instruction.outValue().isDead(appView, code));
}
}
@@ -117,14 +119,25 @@
while (iterator.hasPrevious()) {
Instruction current = iterator.previous();
// Remove unused invoke results.
- if (current.isInvoke()
- && current.outValue() != null
- && !current.outValue().isUsed()) {
+ if (current.isInvoke() && current.hasOutValue() && !current.outValue().isUsed()) {
current.setOutValue(null);
}
- if (!current.canBeDeadCode(appView, code)) {
+ DeadInstructionResult deadInstructionResult = current.canBeDeadCode(appView, code);
+ if (deadInstructionResult.isNotDead()) {
continue;
}
+ if (deadInstructionResult.isMaybeDead()) {
+ boolean satisfied = true;
+ for (Value valueRequiredToBeDead : deadInstructionResult.getValuesRequiredToBeDead()) {
+ if (!valueRequiredToBeDead.isDead(appView, code)) {
+ satisfied = false;
+ break;
+ }
+ }
+ if (!satisfied) {
+ continue;
+ }
+ }
Value outValue = current.outValue();
if (outValue != null && !outValue.isDead(appView, code)) {
continue;
@@ -207,4 +220,61 @@
}
return builder.build();
}
+
+ public abstract static class DeadInstructionResult {
+
+ private static final DeadInstructionResult DEFINITELY_DEAD_INSTANCE =
+ new DeadInstructionResult() {
+ @Override
+ public boolean isDeadIfOutValueIsDead() {
+ return true;
+ }
+ };
+
+ private static final DeadInstructionResult DEFINITELY_NOT_DEAD_INSTANCE =
+ new DeadInstructionResult() {
+ @Override
+ public boolean isNotDead() {
+ return true;
+ }
+ };
+
+ public static DeadInstructionResult deadIfOutValueIsDead() {
+ return DEFINITELY_DEAD_INSTANCE;
+ }
+
+ public static DeadInstructionResult notDead() {
+ return DEFINITELY_NOT_DEAD_INSTANCE;
+ }
+
+ public static DeadInstructionResult deadIfInValueIsDead(Value inValueRequiredToBeDead) {
+ return new DeadInstructionResult() {
+ @Override
+ public boolean isMaybeDead() {
+ return true;
+ }
+
+ @Override
+ public Iterable<Value> getValuesRequiredToBeDead() {
+ return () -> Iterators.singletonIterator(inValueRequiredToBeDead);
+ }
+ };
+ }
+
+ public boolean isDeadIfOutValueIsDead() {
+ return false;
+ }
+
+ public boolean isNotDead() {
+ return false;
+ }
+
+ public boolean isMaybeDead() {
+ return false;
+ }
+
+ public Iterable<Value> getValuesRequiredToBeDead() {
+ throw new Unreachable();
+ }
+ }
}
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 c4bf62f..f1ed11b 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
@@ -265,7 +265,8 @@
return null;
}
- if (inliner.isBlacklisted(singleTarget, whyAreYouNotInliningReporter)) {
+ if (inliner.isBlacklisted(
+ invoke, resolutionResult, singleTarget, whyAreYouNotInliningReporter)) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 7752d6c..cb8f858 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -53,6 +53,7 @@
public void devirtualizeInvokeInterface(IRCode code) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
+ AssumeRemover assumeRemover = new AssumeRemover(appView, code);
ProgramMethod context = code.context();
Map<InvokeInterface, InvokeVirtual> devirtualizedCall = new IdentityHashMap<>();
DominatorTree dominatorTree = new DominatorTree(code);
@@ -76,7 +77,7 @@
// (out <-) invoke-virtual rcv_c, ... C#foo
// ...
// non_null_rcv <- non-null rcv_c // <- Update the input rcv to the non-null, too.
- if (current.isAssumeNonNull()) {
+ if (current.isAssumeWithNonNullAssumption()) {
Assume nonNull = current.asAssume();
Instruction origin = nonNull.origin();
if (origin.isInvokeInterface()
@@ -253,8 +254,8 @@
it.next();
}
}
-
affectedValues.addAll(receiver.affectedValues());
+ assumeRemover.markAssumeDynamicTypeUsersForRemoval(receiver);
if (!receiver.hasLocalInfo()) {
receiver.replaceSelectiveUsers(
newReceiver, ImmutableSet.of(devirtualizedInvoke), ImmutableMap.of());
@@ -266,6 +267,8 @@
}
}
}
+ assumeRemover.removeMarkedInstructions();
+ affectedValues.addAll(assumeRemover.getAffectedValues());
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 6e2d9c7..20e0515 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -4,36 +4,19 @@
package com.android.tools.r8.ir.optimize;
-import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.google.common.base.Predicates.alwaysTrue;
-
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
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.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.JumpInstruction;
-import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.Timing;
import java.util.ArrayList;
import java.util.List;
-import java.util.ListIterator;
-import java.util.function.Predicate;
-public class DynamicTypeOptimization implements Assumer {
+public class DynamicTypeOptimization {
private final AppView<AppInfoWithLiveness> appView;
@@ -41,125 +24,6 @@
this.appView = appView;
}
- @Override
- public void insertAssumeInstructions(IRCode code, Timing timing) {
- insertAssumeInstructionsInBlocks(code, code.listIterator(), alwaysTrue(), timing);
- }
-
- @Override
- public void insertAssumeInstructionsInBlocks(
- IRCode code,
- BasicBlockIterator blockIterator,
- Predicate<BasicBlock> blockTester,
- Timing timing) {
- timing.begin("Insert assume dynamic type instructions");
- while (blockIterator.hasNext()) {
- BasicBlock block = blockIterator.next();
- if (blockTester.test(block)) {
- insertAssumeDynamicTypeInstructionsInBlock(code, blockIterator, block);
- }
- }
- timing.end();
- }
-
- // TODO(b/127461806): Should also insert AssumeDynamicType instructions after instanceof
- // instructions.
- private void insertAssumeDynamicTypeInstructionsInBlock(
- IRCode code, ListIterator<BasicBlock> blockIterator, BasicBlock block) {
- InstructionListIterator instructionIterator = block.listIterator(code);
- while (instructionIterator.hasNext()) {
- Instruction current = instructionIterator.next();
- if (!current.hasOutValue() || !current.outValue().isUsed()) {
- continue;
- }
-
- TypeElement dynamicUpperBoundType;
- ClassTypeElement dynamicLowerBoundType;
- if (current.isInvokeMethod()) {
- InvokeMethod invoke = current.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
-
- DexType staticReturnTypeRaw = invokedMethod.proto.returnType;
- if (!staticReturnTypeRaw.isReferenceType()) {
- continue;
- }
-
- if (invokedMethod.holder.isArrayType()
- && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) {
- dynamicUpperBoundType =
- TypeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
- dynamicLowerBoundType = null;
- } else {
- DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
- if (singleTarget == null) {
- continue;
- }
-
- MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
- if (optimizationInfo.returnsArgument()) {
- // Don't insert an assume-instruction since we will replace all usages of the out-value
- // by the corresponding argument.
- continue;
- }
-
- dynamicUpperBoundType = optimizationInfo.getDynamicUpperBoundType();
- dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
- }
- } else if (current.isStaticGet()) {
- StaticGet staticGet = current.asStaticGet();
- DexEncodedField encodedField =
- appView.appInfo().resolveField(staticGet.getField()).getResolvedField();
- if (encodedField == null) {
- continue;
- }
-
- dynamicUpperBoundType = encodedField.getOptimizationInfo().getDynamicUpperBoundType();
- dynamicLowerBoundType = encodedField.getOptimizationInfo().getDynamicLowerBoundType();
- } else {
- continue;
- }
-
- Value outValue = current.outValue();
- boolean isTrivial =
- (dynamicUpperBoundType == null
- || !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView))
- && dynamicLowerBoundType == null;
- if (isTrivial) {
- continue;
- }
-
- if (dynamicUpperBoundType == null) {
- dynamicUpperBoundType = outValue.getType();
- }
-
- // Split block if needed (only debug instructions are allowed after the throwing
- // instruction, if any).
- BasicBlock insertionBlock =
- block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
-
- // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
- Value specializedOutValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
- outValue.replaceUsers(specializedOutValue);
-
- // Insert AssumeDynamicType instruction.
- Assume assumeInstruction =
- Assume.createAssumeDynamicTypeInstruction(
- dynamicUpperBoundType,
- dynamicLowerBoundType,
- specializedOutValue,
- outValue,
- current,
- appView);
- assumeInstruction.setPosition(
- appView.options().debug ? current.getPosition() : Position.none());
- if (insertionBlock == block) {
- instructionIterator.add(assumeInstruction);
- } else {
- insertionBlock.listIterator(code).add(assumeInstruction);
- }
- }
- }
-
/**
* Computes the dynamic return type of the given method.
*
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 17cea8b..73da99f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -118,29 +118,40 @@
}
boolean isBlacklisted(
- ProgramMethod method, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- DexMethod reference = method.getReference();
- if (method.getDefinition().getOptimizationInfo().forceInline()
- && appView.appInfo().neverInline.contains(reference)) {
+ InvokeMethod invoke,
+ SingleResolutionResult resolutionResult,
+ ProgramMethod singleTarget,
+ WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+ AppInfoWithLiveness appInfo = appView.appInfo();
+ DexMethod singleTargetReference = singleTarget.getReference();
+ if (singleTarget.getDefinition().getOptimizationInfo().forceInline()
+ && appInfo.neverInline.contains(singleTargetReference)) {
throw new Unreachable();
}
- if (appView.appInfo().isPinned(reference)) {
+ if (appInfo.isPinned(singleTargetReference)) {
whyAreYouNotInliningReporter.reportPinned();
return true;
}
- if (blacklist.contains(appView.graphLense().getOriginalMethodSignature(reference))
- || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(reference, appView)) {
+ if (blacklist.contains(appView.graphLense().getOriginalMethodSignature(singleTargetReference))
+ || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(
+ singleTargetReference, appView)) {
whyAreYouNotInliningReporter.reportBlacklisted();
return true;
}
- if (appView.appInfo().neverInline.contains(reference)) {
+ if (appInfo.neverInline.contains(singleTargetReference)) {
whyAreYouNotInliningReporter.reportMarkedAsNeverInline();
return true;
}
+ if (appInfo.noSideEffects.containsKey(invoke.getInvokedMethod())
+ || appInfo.noSideEffects.containsKey(resolutionResult.getResolvedMethod().getReference())
+ || appInfo.noSideEffects.containsKey(singleTargetReference)) {
+ return true;
+ }
+
return false;
}
@@ -932,7 +943,7 @@
OptimizationFeedback feedback,
InliningIRProvider inliningIRProvider,
Timing timing) {
- AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
+ AssumeRemover assumeRemover = new AssumeRemover(appView, code);
Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
BasicBlockIterator blockIterator = code.listIterator();
ClassInitializationAnalysis classInitializationAnalysis =
@@ -1025,7 +1036,7 @@
// Mark AssumeDynamicType instruction for the out-value for removal, if any.
Value outValue = invoke.outValue();
if (outValue != null) {
- assumeDynamicTypeRemover.markUsersForRemoval(outValue);
+ assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
}
boolean inlineeMayHaveInvokeMethod = inlinee.code.metadata().mayHaveInvokeMethod();
@@ -1073,14 +1084,14 @@
IteratorUtils.previousUntil(blockIterator, previous -> previous == block);
blockIterator.next();
}
- } else if (current.isAssumeDynamicType()) {
- assumeDynamicTypeRemover.removeIfMarked(current.asAssume(), iterator);
+ } else if (current.isAssume()) {
+ assumeRemover.removeIfMarked(current.asAssume(), iterator);
}
}
}
assert inlineeStack.isEmpty();
- assumeDynamicTypeRemover.removeMarkedInstructions(blocksToRemove);
- assumeDynamicTypeRemover.finish();
+ assumeRemover.removeMarkedInstructions(blocksToRemove);
+ assumeRemover.finish();
classInitializationAnalysis.finish();
code.removeBlocks(blocksToRemove);
code.removeAllDeadAndTrivialPhis();
@@ -1129,50 +1140,33 @@
BasicBlockIterator blockIterator,
BasicBlock block,
Timing timing) {
- InternalOptions options = appView.options();
- boolean skip =
- !(options.enableDynamicTypeOptimization
- || options.enableNonNullTracking
- || options.enableValuePropagation);
- if (skip) {
- return;
- }
-
BasicBlock state = IteratorUtils.peekNext(blockIterator);
Set<BasicBlock> inlineeBlocks = SetUtils.newIdentityHashSet(inlinee.blocks);
// Run member value propagation on the inlinee blocks.
- if (options.enableValuePropagation) {
+ if (appView.options().enableValuePropagation) {
rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
applyMemberValuePropagationToInlinee(code, blockIterator, block, inlineeBlocks);
}
// Add non-null IRs only to the inlinee blocks.
- if (options.enableNonNullTracking) {
- Assumer nonNullTracker = new AssumeInserter(appView);
- applyAssumerToInlinee(nonNullTracker, code, blockIterator, block, inlineeBlocks, timing);
- }
+ insertAssumeInstructions(code, blockIterator, block, inlineeBlocks, timing);
- // Add dynamic type assumptions only to the inlinee blocks.
- if (options.enableDynamicTypeOptimization) {
- applyAssumerToInlinee(
- new DynamicTypeOptimization(appView), code, blockIterator, block, inlineeBlocks, timing);
- }
// Restore the old state of the iterator.
rewindBlockIteratorToFirstInlineeBlock(blockIterator, state);
// TODO(b/72693244): need a test where refined env in inlinee affects the caller.
}
- private void applyAssumerToInlinee(
- Assumer assumer,
+ private void insertAssumeInstructions(
IRCode code,
BasicBlockIterator blockIterator,
BasicBlock block,
Set<BasicBlock> inlineeBlocks,
Timing timing) {
rewindBlockIteratorToFirstInlineeBlock(blockIterator, block);
- assumer.insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing);
+ new AssumeInserter(appView)
+ .insertAssumeInstructionsInBlocks(code, blockIterator, inlineeBlocks::contains, timing);
assert !blockIterator.hasNext();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 51c1c01..96fd58c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -230,21 +230,23 @@
Set<Value> affectedValues,
ListIterator<BasicBlock> blocks,
InstructionListIterator iterator,
- InvokeMethod current) {
- DexMethod invokedMethod = current.getInvokedMethod();
+ InvokeMethod invoke) {
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod.proto.returnType.isVoidType()) {
+ return;
+ }
+
+ if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
+ return;
+ }
+
DexType invokedHolder = invokedMethod.holder;
if (!invokedHolder.isClassType()) {
return;
}
- DexEncodedMethod target = current.lookupSingleTarget(appView, context);
- if (target != null && target.isInstanceInitializer()) {
- // Member value propagation does not apply to constructors. Removing a call to a constructor
- // that is marked as having no side effects could lead to verification errors, due to
- // uninitialized instances being used.
- return;
- }
- ProguardMemberRuleLookup lookup = lookupMemberRule(target);
+ DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context);
+ ProguardMemberRuleLookup lookup = lookupMemberRule(singleTarget);
if (lookup == null) {
// -assumenosideeffects rules are applied to upward visible and overriding methods, but only
// references that have actual definitions are marked by the root set builder. So, here, we
@@ -253,60 +255,51 @@
appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).getSingleTarget();
lookup = lookupMemberRule(resolutionTarget);
}
- boolean invokeReplaced = false;
- if (lookup != null) {
- boolean hasUsedOutValue = current.hasOutValue() && current.outValue().isUsed();
- if (!hasUsedOutValue) {
- if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS) {
- // Remove invoke if marked as having no side effects and the return value is not used.
- iterator.removeOrReplaceByDebugLocalRead();
- }
- return;
- }
+ if (lookup != null) {
// Check to see if a constant value can be assumed.
// But, if the current matched rule is -assumenosideeffects without the return value, it won't
// be transformed into a replacement instruction. Check if there is -assumevalues rule bound
// to the target.
- if (target != null
+ if (singleTarget != null
&& lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
&& !lookup.rule.hasReturnValue()) {
- ProguardMemberRule rule = appView.appInfo().assumedValues.get(target.toReference());
+ ProguardMemberRule rule = appView.appInfo().assumedValues.get(singleTarget.toReference());
if (rule != null) {
lookup = new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule);
}
}
- invokeReplaced =
- tryConstantReplacementFromProguard(
- code, affectedValues, blocks, iterator, current, lookup);
+ if (tryConstantReplacementFromProguard(
+ code, affectedValues, blocks, iterator, invoke, lookup)) {
+ return;
+ }
}
- if (invokeReplaced || !current.hasOutValue()) {
- return;
- }
+
// No Proguard rule could replace the instruction check for knowledge about the return value.
- if (target == null || !mayPropagateValueFor(target)) {
+ if (singleTarget == null || !mayPropagateValueFor(singleTarget)) {
return;
}
- AbstractValue abstractReturnValue = target.getOptimizationInfo().getAbstractReturnValue();
+ AbstractValue abstractReturnValue = singleTarget.getOptimizationInfo().getAbstractReturnValue();
+
if (abstractReturnValue.isSingleValue()) {
SingleValue singleReturnValue = abstractReturnValue.asSingleValue();
if (singleReturnValue.isMaterializableInContext(appView, context)) {
- BasicBlock block = current.getBlock();
- Position position = current.getPosition();
+ BasicBlock block = invoke.getBlock();
+ Position position = invoke.getPosition();
Instruction replacement =
- singleReturnValue.createMaterializingInstruction(appView, code, current);
- affectedValues.addAll(current.outValue().affectedValues());
- current.moveDebugValues(replacement);
- current.outValue().replaceUsers(replacement.outValue());
- current.setOutValue(null);
+ singleReturnValue.createMaterializingInstruction(appView, code, invoke);
+ affectedValues.addAll(invoke.outValue().affectedValues());
+ invoke.moveDebugValues(replacement);
+ invoke.outValue().replaceUsers(replacement.outValue());
+ invoke.setOutValue(null);
- if (current.isInvokeMethodWithReceiver()) {
- replaceInstructionByNullCheckIfPossible(current, iterator, context);
- } else if (current.isInvokeStatic()) {
+ if (invoke.isInvokeMethodWithReceiver()) {
+ replaceInstructionByNullCheckIfPossible(invoke, iterator, context);
+ } else if (invoke.isInvokeStatic()) {
replaceInstructionByInitClassIfPossible(
- current, target.holder(), code, iterator, context);
+ invoke, singleTarget.holder(), code, iterator, context);
}
// Insert the definition of the replacement.
@@ -316,7 +309,7 @@
} else {
iterator.add(replacement);
}
- target.getMutableOptimizationInfo().markAsPropagated();
+ singleTarget.getMutableOptimizationInfo().markAsPropagated();
}
}
}
@@ -358,9 +351,7 @@
}
AbstractValue abstractValue;
- if (field.type.isAlwaysNull(appView)) {
- abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
- } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
+ if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
abstractValue = target.getOptimizationInfo().getAbstractValue();
if (abstractValue.isUnknown() && !target.isStatic()) {
AbstractValue abstractReceiverValue =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index ef81097..5178020 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -155,7 +155,7 @@
return appView.appInfo().withLiveness().resolveField(field).getResolvedField();
}
if (field.holder == method.getHolderType()) {
- return appView.definitionFor(field);
+ return method.getHolder().lookupField(field);
}
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index d0ad73a..263cadd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -35,8 +35,9 @@
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -72,8 +73,6 @@
private static final String SERVICE_LOADER_METHOD_PREFIX_NAME = "$load";
private AtomicReference<DexProgramClass> synthesizedClass = new AtomicReference<>();
- private ConcurrentHashMap<DexType, DexEncodedMethod> synthesizedServiceLoaders =
- new ConcurrentHashMap<>();
private final AppView<? extends AppInfoWithLiveness> appView;
@@ -88,6 +87,9 @@
public void rewrite(IRCode code, MethodProcessingId methodProcessingId) {
DexItemFactory factory = appView.dexItemFactory();
InstructionListIterator instructionIterator = code.instructionListIterator();
+ // Create a map from service type to loader methods local to this context since two
+ // service loader calls to the same type in different methods and in the same wave can race.
+ Map<DexType, DexEncodedMethod> synthesizedServiceLoaders = new IdentityHashMap<>();
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 85f0274..6a8b6a5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -7,7 +7,6 @@
import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.ALLOW_ARGUMENT_REMOVAL;
import static com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.Strategy.DISALLOW_ARGUMENT_REMOVAL;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -15,22 +14,17 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldAccessInfo;
+import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
-import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.FieldInstruction;
-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.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
-import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.optimize.MemberPoolCollection.MemberPool;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.Timing;
@@ -40,11 +34,9 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
-import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
-import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -104,6 +96,33 @@
this.appView = appView;
}
+ public UninstantiatedTypeOptimization strenghtenOptimizationInfo() {
+ OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
+ FieldAccessInfoCollection<?> fieldAccessInfoCollection =
+ appView.appInfo().getFieldAccessInfoCollection();
+ AbstractValue nullValue = appView.abstractValueFactory().createSingleNumberValue(0);
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ clazz.forEachField(
+ field -> {
+ if (field.type().isAlwaysNull(appView)) {
+ FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+ if (fieldAccessInfo != null) {
+ // Clear all writes since each write must write `null` to the field.
+ fieldAccessInfo.asMutable().clearWrites();
+ }
+ feedback.recordFieldHasAbstractValue(field, appView, nullValue);
+ }
+ });
+ clazz.forEachMethod(
+ method -> {
+ if (method.returnType().isAlwaysNull(appView)) {
+ feedback.methodReturnsAbstractValue(method, appView, nullValue);
+ }
+ });
+ }
+ return this;
+ }
+
public UninstantiatedTypeOptimizationGraphLense run(
MethodPoolCollection methodPoolCollection, ExecutorService executorService, Timing timing) {
try {
@@ -324,124 +343,4 @@
return dexItemFactory.createMethod(method.holder, newProto, method.name);
}
-
- public void rewrite(IRCode code) {
- AssumeDynamicTypeRemover assumeDynamicTypeRemover = new AssumeDynamicTypeRemover(appView, code);
- Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
- ListIterator<BasicBlock> blockIterator = code.listIterator();
- Set<Value> valuesToNarrow = Sets.newIdentityHashSet();
- while (blockIterator.hasNext()) {
- BasicBlock block = blockIterator.next();
- if (blocksToBeRemoved.contains(block)) {
- continue;
- }
- InstructionListIterator instructionIterator = block.listIterator(code);
- while (instructionIterator.hasNext()) {
- Instruction instruction = instructionIterator.next();
- if (instruction.throwsOnNullInput()) {
- Value couldBeNullValue = instruction.getNonNullInput();
- if (isThrowNullCandidate(couldBeNullValue, instruction, appView, code.context())) {
- instructionIterator.replaceCurrentInstructionWithThrowNull(
- appView, code, blockIterator, blocksToBeRemoved, valuesToNarrow);
- continue;
- }
- }
- if (instruction.isInvokeMethod()) {
- rewriteInvoke(
- instruction.asInvokeMethod(),
- blockIterator,
- instructionIterator,
- code,
- assumeDynamicTypeRemover,
- blocksToBeRemoved,
- valuesToNarrow);
- }
- }
- }
- assumeDynamicTypeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
- code.removeBlocks(blocksToBeRemoved);
- code.removeAllDeadAndTrivialPhis(valuesToNarrow);
- code.removeUnreachableBlocks();
- if (!valuesToNarrow.isEmpty()) {
- new TypeAnalysis(appView).narrowing(valuesToNarrow);
- }
- assert code.isConsistentSSA();
- }
-
- private static boolean isThrowNullCandidate(
- Value couldBeNullValue,
- Instruction current,
- AppView<? extends AppInfoWithClassHierarchy> appView,
- ProgramMethod context) {
- if (!couldBeNullValue.isAlwaysNull(appView)) {
- return false;
- }
- if (current.isFieldInstruction()) {
- // Other resolution-related errors come first.
- FieldInstruction fieldInstruction = current.asFieldInstruction();
- // We can't replace the current instruction with `throw null` if it may throw another
- // exception than NullPointerException.
- if (fieldInstruction.instructionInstanceCanThrow(
- appView, context, SideEffectAssumption.RECEIVER_NOT_NULL)) {
- return false;
- }
- }
- return true;
- }
-
- // invoke instructions with a null receiver has already been rewritten to `throw null`.
- // At this point, we attempt to explore non-null-param-or-throw optimization info and replace
- // the invocation with `throw null` if an argument is known to be null and the method is going to
- // throw for that null argument.
- private void rewriteInvoke(
- InvokeMethod invoke,
- ListIterator<BasicBlock> blockIterator,
- InstructionListIterator instructionIterator,
- IRCode code,
- AssumeDynamicTypeRemover assumeDynamicTypeRemover,
- Set<BasicBlock> blocksToBeRemoved,
- Set<Value> affectedValues) {
- DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.context());
- if (target == null) {
- return;
- }
-
- BitSet facts = target.getOptimizationInfo().getNonNullParamOrThrow();
- if (facts != null) {
- for (int i = 0; i < invoke.arguments().size(); i++) {
- Value argument = invoke.arguments().get(i);
- if (argument.isAlwaysNull(appView) && facts.get(i)) {
- instructionIterator.replaceCurrentInstructionWithThrowNull(
- appView, code, blockIterator, blocksToBeRemoved, affectedValues);
- return;
- }
- }
- }
-
- DexType returnType = target.method.proto.returnType;
- if (returnType.isAlwaysNull(appView)) {
- replaceOutValueByNull(
- invoke, instructionIterator, code, assumeDynamicTypeRemover, affectedValues);
- }
- }
-
- private void replaceOutValueByNull(
- Instruction instruction,
- InstructionListIterator instructionIterator,
- IRCode code,
- AssumeDynamicTypeRemover assumeDynamicTypeRemover,
- Set<Value> affectedValues) {
- assert instructionIterator.peekPrevious() == instruction;
- if (instruction.hasOutValue()) {
- Value outValue = instruction.outValue();
- if (outValue.numberOfAllUsers() > 0) {
- assumeDynamicTypeRemover.markUsersForRemoval(outValue);
- instructionIterator.previous();
- affectedValues.addAll(outValue.affectedValues());
- outValue.replaceUsers(
- instructionIterator.insertConstNullInstruction(code, appView.options()));
- instructionIterator.next();
- }
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 379fc28..c5cd89d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.enums;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import com.android.tools.r8.graph.AppView;
@@ -38,6 +39,7 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.If;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.MemberType;
@@ -325,6 +327,8 @@
return;
}
ImmutableSet<DexType> enumsToUnbox = ImmutableSet.copyOf(this.enumsUnboxingCandidates.keySet());
+ // Update keep info on any of the enum methods of the removed classes.
+ updatePinnedItems(enumsToUnbox);
enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumsToUnbox);
NestedGraphLense enumUnboxingLens = new TreeFixer(enumsToUnbox).fixupTypeReferences();
appView.setUnboxedEnums(enumUnboxerRewriter.getEnumsToUnbox());
@@ -368,6 +372,21 @@
postBuilder.rewrittenWithLens(appView, previousLens);
}
+ private void updatePinnedItems(Set<DexType> enumsToUnbox) {
+ appView
+ .appInfo()
+ .getKeepInfo()
+ .mutate(
+ keepInfo -> {
+ for (DexType type : enumsToUnbox) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ assert !keepInfo.getClassInfo(clazz).isPinned();
+ clazz.forEachProgramMethod(keepInfo::unsafeUnpinMethod);
+ clazz.forEachField(field -> keepInfo.unsafeUnpinField(clazz, field));
+ }
+ });
+ }
+
public void finishAnalysis() {
for (DexType toUnbox : enumsUnboxingCandidates.keySet()) {
DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
@@ -418,6 +437,7 @@
DexMethod singleTarget = encodedSingleTarget.method;
DexClass dexClass = appView.definitionFor(singleTarget.holder);
if (dexClass == null) {
+ assert false;
return Reason.INVALID_INVOKE;
}
if (dexClass.isProgramClass()) {
@@ -429,14 +449,22 @@
return Reason.INVALID_INIT;
}
}
+ // Check that the enum-value only flows into parameters whose type exactly matches the
+ // enum's type.
int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic());
for (int i = 0; i < singleTarget.proto.parameters.size(); i++) {
- if (invokeMethod.inValues().get(offset + i) == enumValue) {
+ if (invokeMethod.getArgument(offset + i) == enumValue) {
if (singleTarget.proto.parameters.values[i].toBaseType(factory) != enumClass.type) {
return Reason.GENERIC_INVOKE;
}
}
}
+ if (invokeMethod.isInvokeMethodWithReceiver()) {
+ Value receiver = invokeMethod.asInvokeMethodWithReceiver().getReceiver();
+ if (receiver == enumValue && dexClass.isInterface()) {
+ return Reason.DEFAULT_METHOD_INVOKE;
+ }
+ }
return Reason.ELIGIBLE;
}
if (dexClass.isClasspathClass()) {
@@ -457,6 +485,8 @@
return Reason.ELIGIBLE;
} else if (singleTarget == factory.enumMethods.ordinal) {
return Reason.ELIGIBLE;
+ } else if (singleTarget == factory.enumMethods.hashCode) {
+ return Reason.ELIGIBLE;
} else if (singleTarget == factory.enumMethods.constructor) {
// Enum constructor call is allowed only if first call of an enum initializer.
if (code.method().isInstanceInitializer()
@@ -645,10 +675,9 @@
INTERFACE,
INSTANCE_FIELD,
GENERIC_INVOKE,
+ DEFAULT_METHOD_INVOKE,
UNEXPECTED_STATIC_FIELD,
UNRESOLVABLE_FIELD,
- VIRTUAL_METHOD,
- UNEXPECTED_DIRECT_METHOD,
CONST_CLASS,
INVALID_PHI,
NO_INIT,
@@ -697,7 +726,6 @@
if (m.isInitializer()) {
clearEnumToUnboxMethod(m);
} else {
- assert m.isStatic();
unboxedEnumsMethods.add(
fixupEncodedMethodToUtility(m, factory.enumUnboxingUtilityType));
methodsToRemove.add(m);
@@ -717,7 +745,7 @@
appView.definitionForProgramType(factory.enumUnboxingUtilityType);
assert utilityClass != null : "Should have been synthesized upfront";
utilityClass.addDirectMethods(unboxedEnumsMethods);
- return lensBuilder.build(factory, appView.graphLense());
+ return lensBuilder.build(factory, appView.graphLense(), enumsToUnbox);
}
private void clearEnumToUnboxMethod(DexEncodedMethod enumMethod) {
@@ -726,11 +754,7 @@
// enumUnboxerRewriter will generate invalid code.
// To work around this problem we clear such methods, i.e., we replace the code object by
// an empty throwing code object, so reprocessing won't take time and will be valid.
- enumMethod.setCode(
- appView.options().isGeneratingClassFiles()
- ? enumMethod.buildEmptyThrowingCfCode()
- : enumMethod.buildEmptyThrowingDexCode(),
- appView);
+ enumMethod.setCode(enumMethod.buildEmptyThrowingCode(appView.options()), appView);
}
private DexEncodedMethod fixupEncodedMethodToUtility(
@@ -738,17 +762,29 @@
DexMethod method = encodedMethod.method;
DexString newMethodName =
factory.createString(
- enumUnboxerRewriter.compatibleName(method.holder) + "$$" + method.name.toString());
- DexMethod newMethod = fixupMethod(method, newHolder, newMethodName);
- lensBuilder.move(method, newMethod, encodedMethod.isStatic());
+ enumUnboxerRewriter.compatibleName(method.holder)
+ + "$"
+ + (encodedMethod.isDirectMethod() ? "d" : "v")
+ + "$"
+ + method.name.toString());
+ DexProto proto =
+ encodedMethod.isStatic()
+ ? method.proto
+ : factory.prependTypeToProto(method.holder, method.proto);
+ DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
+ lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true);
encodedMethod.accessFlags.promoteToPublic();
+ encodedMethod.accessFlags.promoteToStatic();
+ encodedMethod.clearAnnotations();
+ encodedMethod.clearParameterAnnotations();
return encodedMethod.toTypeSubstitutedMethod(newMethod);
}
private DexEncodedMethod fixupEncodedMethod(DexEncodedMethod encodedMethod) {
DexMethod newMethod = fixupMethod(encodedMethod.method);
if (newMethod != encodedMethod.method) {
- lensBuilder.move(encodedMethod.method, newMethod, encodedMethod.isStatic());
+ boolean isStatic = encodedMethod.isStatic();
+ lensBuilder.move(encodedMethod.method, isStatic, newMethod, isStatic);
return encodedMethod.toTypeSubstitutedMethod(newMethod);
}
return encodedMethod;
@@ -819,6 +855,7 @@
private static class EnumUnboxingLens extends NestedGraphLense {
private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges;
+ private final Set<DexType> unboxedEnums;
EnumUnboxingLens(
Map<DexType, DexType> typeMap,
@@ -828,7 +865,8 @@
BiMap<DexMethod, DexMethod> originalMethodSignatures,
GraphLense previousLense,
DexItemFactory dexItemFactory,
- Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges) {
+ Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges,
+ Set<DexType> unboxedEnums) {
super(
typeMap,
methodMap,
@@ -838,6 +876,7 @@
previousLense,
dexItemFactory);
this.prototypeChanges = prototypeChanges;
+ this.unboxedEnums = unboxedEnums;
}
@Override
@@ -849,6 +888,17 @@
return prototypeChanges.getOrDefault(method, RewrittenPrototypeDescription.none());
}
+ @Override
+ protected Invoke.Type mapInvocationType(
+ DexMethod newMethod, DexMethod originalMethod, Invoke.Type type) {
+ if (unboxedEnums.contains(originalMethod.holder)) {
+ // Methods moved from unboxed enums to the utility class are either static or statified.
+ assert newMethod != originalMethod;
+ return Invoke.Type.STATIC;
+ }
+ return type;
+ }
+
public static Builder builder() {
return new Builder();
}
@@ -858,15 +908,23 @@
private Map<DexMethod, RewrittenPrototypeDescription> prototypeChanges =
new IdentityHashMap<>();
- public void move(DexMethod from, DexMethod to, boolean isStatic) {
+ public void move(DexMethod from, boolean fromStatic, DexMethod to, boolean toStatic) {
super.move(from, to);
- int offset = BooleanUtils.intValue(!isStatic);
+ int offsetDiff = 0;
+ int toOffset = BooleanUtils.intValue(!toStatic);
ArgumentInfoCollection.Builder builder = ArgumentInfoCollection.builder();
+ if (fromStatic != toStatic) {
+ assert toStatic;
+ offsetDiff = 1;
+ builder.addArgumentInfo(
+ 0, new RewrittenTypeInfo(from.holder, to.proto.parameters.values[0]));
+ }
for (int i = 0; i < from.proto.parameters.size(); i++) {
DexType fromType = from.proto.parameters.values[i];
- DexType toType = to.proto.parameters.values[i];
+ DexType toType = to.proto.parameters.values[i + offsetDiff];
if (fromType != toType) {
- builder.addArgumentInfo(i + offset, new RewrittenTypeInfo(fromType, toType));
+ builder.addArgumentInfo(
+ i + offsetDiff + toOffset, new RewrittenTypeInfo(fromType, toType));
}
}
RewrittenTypeInfo returnInfo =
@@ -877,8 +935,8 @@
to, RewrittenPrototypeDescription.createForRewrittenTypes(returnInfo, builder.build()));
}
- @Override
- public EnumUnboxingLens build(DexItemFactory dexItemFactory, GraphLense previousLense) {
+ public EnumUnboxingLens build(
+ DexItemFactory dexItemFactory, GraphLense previousLense, Set<DexType> unboxedEnums) {
if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
return null;
}
@@ -890,7 +948,8 @@
originalMethodSignatures,
previousLense,
dexItemFactory,
- ImmutableMap.copyOf(prototypeChanges));
+ ImmutableMap.copyOf(prototypeChanges),
+ ImmutableSet.copyOf(unboxedEnums));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index b5530c2..08ba8b4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -7,17 +7,15 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMember;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -57,12 +55,6 @@
enumUnboxer.reportFailure(clazz.type, Reason.SUBTYPES);
return false;
}
- // TODO(b/147860220): interfaces without default methods should be acceptable if the build setup
- // is correct (all abstract methods are implemented).
- if (!clazz.interfaces.isEmpty()) {
- enumUnboxer.reportFailure(clazz.type, Reason.INTERFACE);
- return false;
- }
if (!clazz.instanceFields().isEmpty()) {
enumUnboxer.reportFailure(clazz.type, Reason.INSTANCE_FIELD);
return false;
@@ -71,10 +63,6 @@
enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_STATIC_FIELD);
return false;
}
- if (clazz.getMethodCollection().hasVirtualMethods()) {
- enumUnboxer.reportFailure(clazz.type, Reason.VIRTUAL_METHOD);
- return false;
- }
EnumValueInfoMap enumValueInfoMap =
appView.appInfo().withLiveness().getEnumValueInfoMap(clazz.type);
if (enumValueInfoMap == null) {
@@ -85,8 +73,8 @@
// TODO(b/155036467): Fail lazily when an unsupported method is not only present but also used.
// Only Enums with default initializers and static methods can be unboxed at the moment.
for (DexEncodedMethod directMethod : clazz.directMethods()) {
- if (!(directMethod.isStatic() || isStandardEnumInitializer(directMethod))) {
- enumUnboxer.reportFailure(clazz.type, Reason.UNEXPECTED_DIRECT_METHOD);
+ if (directMethod.isInstanceInitializer() && !isStandardEnumInitializer(directMethod)) {
+ enumUnboxer.reportFailure(clazz.type, Reason.INVALID_INIT);
return false;
}
}
@@ -146,22 +134,17 @@
// A holder type, for field or method, should block enum unboxing only if the enum type is
// also kept. This is to allow the keep rule -keepclassmembers to be used on enums while
// enum unboxing can still be performed.
- for (DexReference item : appView.appInfo().getPinnedItems()) {
- if (item.isDexType()) {
- removePinnedCandidate(item.asDexType());
- } else if (item.isDexField()) {
- DexField field = item.asDexField();
- removePinnedIfNotHolder(field, field.type);
- } else {
- assert item.isDexMethod();
- DexMethod method = item.asDexMethod();
- DexProto proto = method.proto;
- removePinnedIfNotHolder(method, proto.returnType);
- for (DexType parameterType : proto.parameters.values) {
- removePinnedIfNotHolder(method, parameterType);
- }
- }
- }
+ KeepInfoCollection keepInfo = appView.appInfo().getKeepInfo();
+ keepInfo.forEachPinnedType(this::removePinnedCandidate);
+ keepInfo.forEachPinnedField(field -> removePinnedIfNotHolder(field, field.type));
+ keepInfo.forEachPinnedMethod(
+ method -> {
+ DexProto proto = method.proto;
+ removePinnedIfNotHolder(method, proto.returnType);
+ for (DexType parameterType : proto.parameters.values) {
+ removePinnedIfNotHolder(method, parameterType);
+ }
+ });
}
private void removePinnedIfNotHolder(DexMember<?, ?> member, DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index 541cd92..1832a2a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -129,7 +129,8 @@
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
if (enumType != null) {
- if (invokedMethod == factory.enumMethods.ordinal) {
+ if (invokedMethod == factory.enumMethods.ordinal
+ || invokedMethod == factory.enumMethods.hashCode) {
replaceEnumInvoke(
iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
continue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
index 1d6668c..2cd8633 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumValueOptimizer.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.optimize.enums;
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,6 +17,7 @@
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -61,6 +64,7 @@
return;
}
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
InstructionListIterator iterator = code.instructionListIterator();
while (iterator.hasNext()) {
Instruction current = iterator.next();
@@ -103,8 +107,12 @@
if (isOrdinalInvoke) {
iterator.replaceCurrentInstruction(new ConstNumber(outValue, valueInfo.ordinal));
} else if (isNameInvoke) {
+ Value newValue =
+ code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
iterator.replaceCurrentInstruction(
- new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+ new ConstString(
+ newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
+ newValue.addAffectedValuesTo(affectedValues);
} else {
assert isToStringInvoke;
DexClass enumClazz = appView.appInfo().definitionFor(enumField.type);
@@ -119,12 +127,16 @@
if (singleTarget != null && singleTarget.method != factory.enumMethods.toString) {
continue;
}
+ Value newValue =
+ code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
iterator.replaceCurrentInstruction(
- new ConstString(outValue, enumField.name, ThrowingInfo.NO_THROW));
+ new ConstString(
+ newValue, enumField.name, ThrowingInfo.defaultForConstString(appView.options())));
+ newValue.addAffectedValuesTo(affectedValues);
}
} else if (current.isArrayLength()) {
// Rewrites MyEnum.values().length to a constant int.
- Instruction arrayDefinition = current.asArrayLength().array().definition;
+ Instruction arrayDefinition = current.asArrayLength().array().getAliasedValue().definition;
if (arrayDefinition != null && arrayDefinition.isInvokeStatic()) {
DexMethod invokedMethod = arrayDefinition.asInvokeStatic().getInvokedMethod();
DexProgramClass enumClass = appView.definitionForProgramType(invokedMethod.holder);
@@ -140,6 +152,9 @@
}
}
}
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
assert code.isConsistentSSA();
}
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 59e24a6..0e3e339 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
@@ -18,7 +18,7 @@
import java.util.BitSet;
import java.util.Set;
-public class DefaultMethodOptimizationInfo implements MethodOptimizationInfo {
+public class DefaultMethodOptimizationInfo extends MethodOptimizationInfo {
public static final MethodOptimizationInfo DEFAULT_INSTANCE = new DefaultMethodOptimizationInfo();
@@ -124,11 +124,6 @@
}
@Override
- public boolean neverReturnsNull() {
- return UNKNOWN_NEVER_RETURNS_NULL;
- }
-
- @Override
public boolean neverReturnsNormally() {
return UNKNOWN_NEVER_RETURNS_NORMALLY;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 09ad5e5..0365122 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -26,6 +26,11 @@
public abstract TypeElement getDynamicUpperBoundType();
+ public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) {
+ TypeElement dynamicUpperBoundType = getDynamicUpperBoundType();
+ return dynamicUpperBoundType != null ? dynamicUpperBoundType : orElse;
+ }
+
public abstract boolean isDead();
public abstract boolean valueHasBeenPropagated();
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 16ce797..f919a1e 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
@@ -15,7 +15,7 @@
import java.util.BitSet;
import java.util.Set;
-public interface MethodOptimizationInfo {
+public abstract class MethodOptimizationInfo {
enum InlinePreference {
NeverInline,
@@ -23,63 +23,70 @@
Default
}
- boolean isDefaultMethodOptimizationInfo();
+ public abstract boolean isDefaultMethodOptimizationInfo();
- boolean isUpdatableMethodOptimizationInfo();
+ public abstract boolean isUpdatableMethodOptimizationInfo();
- UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo();
+ public abstract UpdatableMethodOptimizationInfo asUpdatableMethodOptimizationInfo();
- boolean cannotBeKept();
+ public abstract boolean cannotBeKept();
- boolean classInitializerMayBePostponed();
+ public abstract boolean classInitializerMayBePostponed();
- TypeElement getDynamicUpperBoundType();
+ public abstract TypeElement getDynamicUpperBoundType();
- ClassTypeElement getDynamicLowerBoundType();
+ public final TypeElement getDynamicUpperBoundTypeOrElse(TypeElement orElse) {
+ TypeElement dynamicUpperBoundType = getDynamicUpperBoundType();
+ return dynamicUpperBoundType != null ? dynamicUpperBoundType : orElse;
+ }
- ParameterUsage getParameterUsages(int parameter);
+ public abstract ClassTypeElement getDynamicLowerBoundType();
- BitSet getNonNullParamOrThrow();
+ public abstract ParameterUsage getParameterUsages(int parameter);
- BitSet getNonNullParamOnNormalExits();
+ public final boolean hasNonNullParamOrThrow() {
+ return getNonNullParamOrThrow() != null;
+ }
- boolean hasBeenInlinedIntoSingleCallSite();
+ public abstract BitSet getNonNullParamOrThrow();
- boolean isReachabilitySensitive();
+ public abstract BitSet getNonNullParamOnNormalExits();
- boolean returnsArgument();
+ public abstract boolean hasBeenInlinedIntoSingleCallSite();
- int getReturnedArgument();
+ public abstract boolean isReachabilitySensitive();
- boolean neverReturnsNull();
+ public abstract boolean returnsArgument();
- boolean neverReturnsNormally();
+ public abstract int getReturnedArgument();
- BridgeInfo getBridgeInfo();
+ public abstract boolean neverReturnsNormally();
- ClassInlinerEligibilityInfo getClassInlinerEligibility();
+ public abstract BridgeInfo getBridgeInfo();
- Set<DexType> getInitializedClassesOnNormalExit();
+ public abstract ClassInlinerEligibilityInfo getClassInlinerEligibility();
- InstanceInitializerInfo getInstanceInitializerInfo();
+ public abstract Set<DexType> getInitializedClassesOnNormalExit();
- boolean isInitializerEnablingJavaVmAssertions();
+ public abstract InstanceInitializerInfo getInstanceInitializerInfo();
- AbstractValue getAbstractReturnValue();
+ public abstract boolean isInitializerEnablingJavaVmAssertions();
- boolean forceInline();
+ public abstract AbstractValue getAbstractReturnValue();
- boolean neverInline();
+ public abstract boolean forceInline();
- boolean checksNullReceiverBeforeAnySideEffect();
+ public abstract boolean neverInline();
- boolean triggersClassInitBeforeAnySideEffect();
+ public abstract boolean checksNullReceiverBeforeAnySideEffect();
- boolean mayHaveSideEffects();
+ public abstract boolean triggersClassInitBeforeAnySideEffect();
- boolean returnValueOnlyDependsOnArguments();
+ public abstract boolean mayHaveSideEffects();
- boolean returnValueHasBeenPropagated();
+ public abstract boolean returnValueOnlyDependsOnArguments();
- UpdatableMethodOptimizationInfo mutableCopy();
+ public abstract boolean returnValueHasBeenPropagated();
+
+ public abstract UpdatableMethodOptimizationInfo mutableCopy();
}
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 cac5801..9e270af 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
@@ -360,14 +360,12 @@
return;
}
Value returnValue = firstExit.returnValue();
- boolean isNeverNull = returnValue.getType().isReferenceType() && returnValue.isNeverNull();
for (int i = 1; i < normalExits.size(); i++) {
Return exit = normalExits.get(i).exit().asReturn();
Value value = exit.returnValue();
if (value != returnValue) {
returnValue = null;
}
- isNeverNull &= value.getType().isReferenceType() && value.isNeverNull();
}
if (returnValue != null) {
Value aliasedValue = returnValue.getAliasedValue();
@@ -382,9 +380,6 @@
}
}
}
- if (isNeverNull) {
- feedback.methodNeverReturnsNull(method);
- }
}
private void computeInstanceInitializerInfo(
@@ -958,21 +953,38 @@
if (!staticReturnTypeRaw.isReferenceType()) {
return;
}
- TypeElement dynamicReturnType =
+ TypeElement dynamicUpperBoundReturnType =
dynamicTypeOptimization.computeDynamicReturnType(method, code);
- if (dynamicReturnType != null) {
- TypeElement staticReturnType =
- TypeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
- // If the dynamic return type is not more precise than the static return type there is no
- // need to record it.
- if (dynamicReturnType.strictlyLessThan(staticReturnType, appView)) {
- feedback.methodReturnsObjectWithUpperBoundType(method, appView, dynamicReturnType);
+ if (dynamicUpperBoundReturnType != null) {
+ if (dynamicUpperBoundReturnType.isReferenceType()
+ && dynamicUpperBoundReturnType.isDefinitelyNull()) {
+ feedback.methodReturnsAbstractValue(
+ method, appView, appView.abstractValueFactory().createSingleNumberValue(0));
+ feedback.methodReturnsObjectWithUpperBoundType(method, appView, TypeElement.getNull());
+ } else {
+ TypeElement staticReturnType =
+ TypeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
+ // If the dynamic return type is not more precise than the static return type there is no
+ // need to record it.
+ if (dynamicUpperBoundReturnType.strictlyLessThan(staticReturnType, appView)) {
+ feedback.methodReturnsObjectWithUpperBoundType(
+ method, appView, dynamicUpperBoundReturnType);
+ }
}
}
- ClassTypeElement exactReturnType =
+
+ if (dynamicUpperBoundReturnType != null && dynamicUpperBoundReturnType.isNullType()) {
+ return;
+ }
+
+ ClassTypeElement dynamicLowerBoundReturnType =
dynamicTypeOptimization.computeDynamicLowerBoundType(method, code);
- if (exactReturnType != null) {
- feedback.methodReturnsObjectWithLowerBoundType(method, exactReturnType);
+ if (dynamicLowerBoundReturnType != null) {
+ assert dynamicUpperBoundReturnType == null
+ || dynamicUpperBoundReturnType
+ .nullability()
+ .lessThanOrEqual(dynamicLowerBoundReturnType.nullability());
+ feedback.methodReturnsObjectWithLowerBoundType(method, dynamicLowerBoundReturnType);
}
}
}
@@ -1156,7 +1168,7 @@
// Collect basic blocks that check nullability of the parameter.
nullCheckedBlocks.clear();
for (Instruction user : argument.uniqueUsers()) {
- if (user.isAssumeNonNull()) {
+ if (user.isAssumeWithNonNullAssumption()) {
nullCheckedBlocks.add(user.asAssume().getBlock());
}
if (user.isIf()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index d4eef20..d9db007 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -201,11 +201,6 @@
}
@Override
- public synchronized void methodNeverReturnsNull(DexEncodedMethod method) {
- getMethodOptimizationInfoForUpdating(method).markNeverReturnsNull();
- }
-
- @Override
public synchronized void methodNeverReturnsNormally(DexEncodedMethod method) {
getMethodOptimizationInfoForUpdating(method).markNeverReturnsNormally();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 435f05f..49ffaf9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -90,9 +90,6 @@
public void methodReturnValueOnlyDependsOnArguments(DexEncodedMethod method) {}
@Override
- public void methodNeverReturnsNull(DexEncodedMethod method) {}
-
- @Override
public void methodNeverReturnsNormally(DexEncodedMethod method) {}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index 9696162..822e0c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -104,7 +104,7 @@
@Override
public void methodReturnsObjectWithUpperBoundType(
DexEncodedMethod method, AppView<?> appView, TypeElement type) {
- // Ignored.
+ method.getMutableOptimizationInfo().markReturnsObjectWithUpperBoundType(appView, type);
}
@Override
@@ -124,11 +124,6 @@
}
@Override
- public void methodNeverReturnsNull(DexEncodedMethod method) {
- method.getMutableOptimizationInfo().markNeverReturnsNull();
- }
-
- @Override
public void methodNeverReturnsNormally(DexEncodedMethod method) {
// Ignored.
}
@@ -140,8 +135,7 @@
@Override
public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {
- // Just as processed, don't provide any inlining constraints.
- method.markProcessed(ConstraintWithTarget.NEVER);
+ method.markProcessed(state);
}
@Override
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 c1012ca..3971c57 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,7 +22,7 @@
import java.util.Set;
import java.util.function.Function;
-public class UpdatableMethodOptimizationInfo implements MethodOptimizationInfo {
+public class UpdatableMethodOptimizationInfo extends MethodOptimizationInfo {
private Set<DexType> initializedClassesOnNormalExit =
DefaultMethodOptimizationInfo.UNKNOWN_INITIALIZED_CLASSES_ON_NORMAL_EXIT;
@@ -71,9 +71,9 @@
private static final int HAS_BEEN_INLINED_INTO_SINGLE_CALL_SITE_FLAG = 0x4;
private static final int MAY_HAVE_SIDE_EFFECT_FLAG = 0x8;
private static final int RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG = 0x10;
- private static final int NEVER_RETURNS_NULL_FLAG = 0x20;
+ private static final int UNUSED_FLAG_1 = 0x20;
private static final int NEVER_RETURNS_NORMALLY_FLAG = 0x40;
- private static final int UNUSED_FLAG = 0x80;
+ private static final int UNUSED_FLAG_2 = 0x80;
private static final int CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x100;
private static final int TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT_FLAG = 0x200;
private static final int INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG = 0x400;
@@ -97,11 +97,10 @@
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.returnValueOnlyDependsOnArguments())
* RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG;
- defaultFlags |=
- BooleanUtils.intValue(defaultOptInfo.neverReturnsNull()) * NEVER_RETURNS_NULL_FLAG;
+ defaultFlags |= 0 * UNUSED_FLAG_1;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.neverReturnsNormally()) * NEVER_RETURNS_NORMALLY_FLAG;
- defaultFlags |= 0 * UNUSED_FLAG;
+ defaultFlags |= 0 * UNUSED_FLAG_2;
defaultFlags |=
BooleanUtils.intValue(defaultOptInfo.checksNullReceiverBeforeAnySideEffect())
* CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT_FLAG;
@@ -293,11 +292,6 @@
}
@Override
- public boolean neverReturnsNull() {
- return isFlagSet(NEVER_RETURNS_NULL_FLAG);
- }
-
- @Override
public boolean neverReturnsNormally() {
return isFlagSet(NEVER_RETURNS_NORMALLY_FLAG);
}
@@ -402,10 +396,6 @@
setFlag(RETURN_VALUE_ONLY_DEPENDS_ON_ARGUMENTS_FLAG);
}
- void markNeverReturnsNull() {
- setFlag(NEVER_RETURNS_NULL_FLAG);
- }
-
void markNeverReturnsNormally() {
setFlag(NEVER_RETURNS_NORMALLY_FLAG);
}
@@ -429,6 +419,9 @@
|| type.lessThanOrEqualUpToNullability(returnsObjectWithUpperBoundType, appView)
: "upper bound type changed from " + returnsObjectWithUpperBoundType + " to " + type;
returnsObjectWithUpperBoundType = type;
+ if (type.isNullType()) {
+ returnsObjectWithLowerBoundType = null;
+ }
}
void markReturnsObjectWithLowerBoundType(ClassTypeElement type) {
@@ -436,9 +429,7 @@
// Currently, we only have a lower bound type when we have _exact_ runtime type information.
// Thus, the type should never become more precise (although the nullability could).
assert returnsObjectWithLowerBoundType == DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE
- || (type.equalUpToNullability(returnsObjectWithLowerBoundType)
- && type.nullability()
- .lessThanOrEqual(returnsObjectWithLowerBoundType.nullability()))
+ || type.equalUpToNullability(returnsObjectWithLowerBoundType)
: "lower bound type changed from " + returnsObjectWithLowerBoundType + " to " + type;
returnsObjectWithLowerBoundType = type;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
index 2fe8a19..fe70918 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/NonTrivialInstanceFieldInitializationInfoCollection.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.ir.optimize.info.field;
+
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.DexEncodedField;
import com.android.tools.r8.graph.DexField;
@@ -32,9 +34,10 @@
BiConsumer<DexEncodedField, InstanceFieldInitializationInfo> consumer) {
infos.forEach(
(field, info) -> {
- DexEncodedField encodedField = definitions.definitionFor(field);
- if (encodedField != null) {
- consumer.accept(encodedField, info);
+ DexClass holder = definitions.definitionForHolder(field);
+ DexEncodedField definition = field.lookupOnClass(holder);
+ if (definition != null) {
+ consumer.accept(definition, info);
} else {
assert false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
index e392112..e9ab954 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -49,7 +49,7 @@
genericSignature,
mainMethod,
innerClass,
- lambda.getEnclosingMethod());
+ lambda.getEnclosingMethodAttribute());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
index ea39dce..495ab0b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -49,7 +49,7 @@
genericSignature,
mainMethod,
innerClass,
- lambda.getEnclosingMethod());
+ lambda.getEnclosingMethodAttribute());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 3a7e099..bcea380 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -44,8 +44,7 @@
register(new ObjectMethodOptimizer(appView));
register(new ObjectsMethodOptimizer(appView));
register(new StringMethodOptimizer(appView));
- if (appView.enableWholeProgramOptimizations()
- && appView.options().enableDynamicTypeOptimization) {
+ if (appView.enableWholeProgramOptimizations()) {
// Subtyping is required to prove the enum class is a subtype of java.lang.Enum.
register(new EnumMethodOptimizer(appView));
}
@@ -66,7 +65,7 @@
for (LibraryMembers libraryMembers : appView.dexItemFactory().libraryMembersCollection) {
libraryMembers.forEachFinalField(
field -> {
- DexEncodedField definition = appView.definitionFor(field);
+ DexEncodedField definition = field.lookupOnClass(appView.definitionForHolder(field));
if (definition != null) {
if (definition.isFinal()) {
finalLibraryFields.add(definition);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index a677b43..92c78af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -4,12 +4,15 @@
package com.android.tools.r8.ir.optimize.library;
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
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.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.ir.analysis.value.ObjectState;
import com.android.tools.r8.ir.optimize.info.LibraryOptimizationInfoInitializerFeedback;
@@ -57,7 +60,16 @@
for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) {
DexEncodedMethod definition = lookupMethod(method);
if (definition != null) {
- feedback.methodNeverReturnsNull(definition);
+ TypeElement staticType =
+ TypeElement.fromDexType(method.proto.returnType, maybeNull(), appView);
+ feedback.methodReturnsObjectWithUpperBoundType(
+ definition,
+ appView,
+ definition
+ .getOptimizationInfo()
+ .getDynamicUpperBoundTypeOrElse(staticType)
+ .asReferenceType()
+ .asDefinitelyNotNull());
}
}
}
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 0e0e1c9..29250dc 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
@@ -35,6 +35,7 @@
import com.android.tools.r8.ir.conversion.MethodProcessingId;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
+import com.android.tools.r8.ir.optimize.AssumeInserter;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
@@ -390,7 +391,10 @@
}
private void insertAssumeInstructions(IRCode code, MethodProcessor methodProcessor) {
- CodeRewriter.insertAssumeInstructions(code, converter.assumers, Timing.empty());
+ AssumeInserter assumeInserter = converter.assumeInserter;
+ if (assumeInserter != null) {
+ assumeInserter.insertAssumeInstructions(code, Timing.empty());
+ }
}
private BiConsumer<IRCode, MethodProcessor> collectOptimizationInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 7eddfcc..7af08f9 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -347,6 +347,7 @@
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
if (!instructionIterator.hasNext()) {
+ instruction.clearDebugValues();
break;
}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
index 61bdbbc..5e9d63f 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.cf.code.CfFieldInstruction;
import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfIfCmp;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfLabel;
@@ -66,7 +67,7 @@
CfLabel dest = new CfLabel();
instructions.add(new CfLoad(ValueType.fromDexType(factory.intType), 0));
instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
- instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest));
+ instructions.add(new CfIfCmp(If.Type.NE, ValueType.INT, dest));
instructions.add(new CfConstString(field.name));
instructions.add(new CfReturn(ValueType.OBJECT));
instructions.add(dest);
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 18de3c5..01c3daa 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -172,8 +172,8 @@
writeAnnotations(writer::visitAnnotation, clazz.annotations().annotations);
ImmutableMap<DexString, DexValue> defaults = getAnnotationDefaults(clazz.annotations());
- if (clazz.getEnclosingMethod() != null) {
- clazz.getEnclosingMethod().write(writer, namingLens);
+ if (clazz.getEnclosingMethodAttribute() != null) {
+ clazz.getEnclosingMethodAttribute().write(writer, namingLens);
}
if (clazz.getNestHostClassAttribute() != null) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
index 84d6e82..7431fb9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
@@ -13,6 +13,7 @@
import kotlinx.metadata.KmTypeVisitor;
import kotlinx.metadata.KmValueParameterVisitor;
import kotlinx.metadata.KmVariance;
+import kotlinx.metadata.KmVersionRequirementVisitor;
/**
* The reason for having these visitor providers is to make the separation of concern a bit easier
@@ -101,4 +102,10 @@
KmTypeVisitor get(int flags, String typeFlexibilityId);
}
+
+ @FunctionalInterface
+ public interface KmVersionRequirementVisitorProvider {
+
+ KmVersionRequirementVisitor get();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 602165c..07f6b0b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -42,7 +42,7 @@
public static final class ClassClassifiers {
public static final String arrayBinaryName = NAME + "/Array";
- public static final String anyName = NAME + "/Any";
+ public static final String anyDescriptor = "L" + NAME + "/Any;";
}
// Mappings from JVM types to Kotlin types (of type DexType)
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
index a4bc5b0..90ed44a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
@@ -4,14 +4,12 @@
package com.android.tools.r8.kotlin;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromBinaryName;
-
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -20,35 +18,33 @@
import kotlinx.metadata.KmAnnotationArgument;
// Holds information about a KmAnnotation
-public class KotlinAnnotationInfo {
+public class KotlinAnnotationInfo implements EnqueuerMetadataTraceable {
private static final List<KotlinAnnotationInfo> EMPTY_ANNOTATIONS = ImmutableList.of();
- private final DexType annotationType;
+ private final KotlinTypeReference annotationType;
// TODO(b/155053894): Model KmAnnotationArgument.
private final Map<String, KmAnnotationArgument<?>> arguments;
private KotlinAnnotationInfo(
- DexType annotationType, Map<String, KmAnnotationArgument<?>> arguments) {
+ KotlinTypeReference annotationType, Map<String, KmAnnotationArgument<?>> arguments) {
this.annotationType = annotationType;
this.arguments = arguments;
}
- private static KotlinAnnotationInfo create(
- KmAnnotation annotation, DexDefinitionSupplier definitionSupplier) {
+ private static KotlinAnnotationInfo create(KmAnnotation annotation, DexItemFactory factory) {
return new KotlinAnnotationInfo(
- referenceTypeFromBinaryName(annotation.getClassName(), definitionSupplier),
+ KotlinTypeReference.fromBinaryName(annotation.getClassName(), factory),
annotation.getArguments());
}
- static List<KotlinAnnotationInfo> create(
- List<KmAnnotation> annotations, DexDefinitionSupplier definitionSupplier) {
+ static List<KotlinAnnotationInfo> create(List<KmAnnotation> annotations, DexItemFactory factory) {
if (annotations.isEmpty()) {
return EMPTY_ANNOTATIONS;
}
ImmutableList.Builder<KotlinAnnotationInfo> builder = ImmutableList.builder();
for (KmAnnotation annotation : annotations) {
- builder.add(create(annotation, definitionSupplier));
+ builder.add(create(annotation, factory));
}
return builder.build();
}
@@ -57,12 +53,20 @@
KmVisitorProviders.KmAnnotationVisitorProvider visitorProvider,
AppView<AppInfoWithLiveness> appView,
NamingLens namingLens) {
- if (appView.appInfo().wasPruned(annotationType)) {
+ String renamedDescriptor =
+ annotationType.toRenamedDescriptorOrDefault(appView, namingLens, null);
+ if (renamedDescriptor == null) {
+ // The type has been pruned
return;
}
- DexString descriptor = namingLens.lookupDescriptor(annotationType);
- String classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString());
+ String classifier = DescriptorUtils.descriptorToKotlinClassifier(renamedDescriptor);
KmAnnotation annotation = new KmAnnotation(classifier, arguments);
visitorProvider.get(annotation);
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ annotationType.trace(definitionSupplier);
+ // TODO(b/155053894): Trace annotation arguments.
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index b507093..9987457 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -4,17 +4,17 @@
package com.android.tools.r8.kotlin;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromBinaryName;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmFieldSignature;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toJvmMethodSignature;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
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.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -41,14 +41,15 @@
private final KotlinDeclarationContainerInfo declarationContainerInfo;
private final List<KotlinTypeParameterInfo> typeParameters;
private final List<KotlinTypeInfo> superTypes;
- private final List<DexType> sealedSubClasses;
- private final List<DexType> nestedClasses;
+ private final List<KotlinTypeReference> sealedSubClasses;
+ private final List<KotlinTypeReference> nestedClasses;
// TODO(b/154347404): Understand enum entries.
private final List<String> enumEntries;
- private final DexType anonymousObjectOrigin;
+ private final KotlinVersionRequirementInfo versionRequirements;
+ private final KotlinTypeReference anonymousObjectOrigin;
private final String packageName;
- public KotlinClassInfo(
+ private KotlinClassInfo(
int flags,
String name,
String moduleName,
@@ -56,10 +57,11 @@
List<KotlinTypeParameterInfo> typeParameters,
List<KotlinConstructorInfo> constructorsWithNoBacking,
List<KotlinTypeInfo> superTypes,
- List<DexType> sealedSubClasses,
- List<DexType> nestedClasses,
+ List<KotlinTypeReference> sealedSubClasses,
+ List<KotlinTypeReference> nestedClasses,
List<String> enumEntries,
- DexType anonymousObjectOrigin,
+ KotlinVersionRequirementInfo versionRequirements,
+ KotlinTypeReference anonymousObjectOrigin,
String packageName) {
this.flags = flags;
this.name = name;
@@ -71,6 +73,7 @@
this.sealedSubClasses = sealedSubClasses;
this.nestedClasses = nestedClasses;
this.enumEntries = enumEntries;
+ this.versionRequirements = versionRequirements;
this.anonymousObjectOrigin = anonymousObjectOrigin;
this.packageName = packageName;
}
@@ -79,7 +82,7 @@
KmClass kmClass,
String packageName,
DexClass hostClass,
- DexDefinitionSupplier definitionSupplier,
+ DexItemFactory factory,
Reporter reporter,
Consumer<DexEncodedMethod> keepByteCode) {
Map<String, DexEncodedField> fieldMap = new HashMap<>();
@@ -93,7 +96,7 @@
ImmutableList.Builder<KotlinConstructorInfo> notBackedConstructors = ImmutableList.builder();
for (KmConstructor kmConstructor : kmClass.getConstructors()) {
KotlinConstructorInfo constructorInfo =
- KotlinConstructorInfo.create(kmConstructor, definitionSupplier, reporter);
+ KotlinConstructorInfo.create(kmConstructor, factory, reporter);
JvmMethodSignature signature = JvmExtensionsKt.getSignature(kmConstructor);
if (signature != null) {
DexEncodedMethod method = methodMap.get(signature.asString());
@@ -107,60 +110,61 @@
}
KotlinDeclarationContainerInfo container =
KotlinDeclarationContainerInfo.create(
- kmClass, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode);
+ kmClass, methodMap, fieldMap, factory, reporter, keepByteCode);
setCompanionObject(kmClass, hostClass, reporter);
return new KotlinClassInfo(
kmClass.getFlags(),
kmClass.name,
JvmExtensionsKt.getModuleName(kmClass),
container,
- KotlinTypeParameterInfo.create(kmClass.getTypeParameters(), definitionSupplier, reporter),
+ KotlinTypeParameterInfo.create(kmClass.getTypeParameters(), factory, reporter),
notBackedConstructors.build(),
- getSuperTypes(kmClass.getSupertypes(), definitionSupplier, reporter),
- getSealedSubClasses(hostClass, kmClass.getSealedSubclasses(), definitionSupplier),
- getNestedClasses(hostClass, kmClass.getNestedClasses(), definitionSupplier),
+ getSuperTypes(kmClass.getSupertypes(), factory, reporter),
+ getSealedSubClasses(kmClass.getSealedSubclasses(), factory),
+ getNestedClasses(hostClass, kmClass.getNestedClasses(), factory),
kmClass.getEnumEntries(),
- getAnonymousObjectOrigin(kmClass, definitionSupplier),
+ KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()),
+ getAnonymousObjectOrigin(kmClass, factory),
packageName);
}
- private static DexType getAnonymousObjectOrigin(
- KmClass kmClass, DexDefinitionSupplier definitionSupplier) {
+ private static KotlinTypeReference getAnonymousObjectOrigin(
+ KmClass kmClass, DexItemFactory factory) {
String anonymousObjectOriginName = JvmExtensionsKt.getAnonymousObjectOriginName(kmClass);
if (anonymousObjectOriginName != null) {
- return referenceTypeFromBinaryName(anonymousObjectOriginName, definitionSupplier);
+ return KotlinTypeReference.fromBinaryName(anonymousObjectOriginName, factory);
}
return null;
}
- private static List<DexType> getNestedClasses(
- DexClass clazz, List<String> nestedClasses, DexDefinitionSupplier definitionSupplier) {
- ImmutableList.Builder<DexType> nestedTypes = ImmutableList.builder();
+ private static List<KotlinTypeReference> getNestedClasses(
+ DexClass clazz, List<String> nestedClasses, DexItemFactory factory) {
+ ImmutableList.Builder<KotlinTypeReference> nestedTypes = ImmutableList.builder();
for (String nestedClass : nestedClasses) {
String binaryName =
clazz.type.toBinaryName() + DescriptorUtils.INNER_CLASS_SEPARATOR + nestedClass;
- nestedTypes.add(referenceTypeFromBinaryName(binaryName, definitionSupplier));
+ nestedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory));
}
return nestedTypes.build();
}
- private static List<DexType> getSealedSubClasses(
- DexClass clazz, List<String> sealedSubclasses, DexDefinitionSupplier definitionSupplier) {
- ImmutableList.Builder<DexType> sealedTypes = ImmutableList.builder();
+ private static List<KotlinTypeReference> getSealedSubClasses(
+ List<String> sealedSubclasses, DexItemFactory factory) {
+ ImmutableList.Builder<KotlinTypeReference> sealedTypes = ImmutableList.builder();
for (String sealedSubClass : sealedSubclasses) {
String binaryName =
sealedSubClass.replace(
DescriptorUtils.JAVA_PACKAGE_SEPARATOR, DescriptorUtils.INNER_CLASS_SEPARATOR);
- sealedTypes.add(referenceTypeFromBinaryName(binaryName, definitionSupplier));
+ sealedTypes.add(KotlinTypeReference.fromBinaryName(binaryName, factory));
}
return sealedTypes.build();
}
private static List<KotlinTypeInfo> getSuperTypes(
- List<KmType> superTypes, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+ List<KmType> superTypes, DexItemFactory factory, Reporter reporter) {
ImmutableList.Builder<KotlinTypeInfo> superTypeInfos = ImmutableList.builder();
for (KmType superType : superTypes) {
- superTypeInfos.add(KotlinTypeInfo.create(superType, definitionSupplier, reporter));
+ superTypeInfos.add(KotlinTypeInfo.create(superType, factory, reporter));
}
return superTypeInfos.build();
}
@@ -205,7 +209,7 @@
kmClass.setName(
originalDescriptor.equals(rewrittenDescriptor)
? this.name
- : KotlinMetadataUtils.kotlinNameFromDescriptor(rewrittenDescriptor));
+ : DescriptorUtils.getBinaryNameFromDescriptor(rewrittenDescriptor.toString()));
// Find a companion object.
for (DexEncodedField field : clazz.fields()) {
if (field.getKotlinMemberInfo().isCompanion()) {
@@ -240,34 +244,35 @@
superType.rewrite(kmClass::visitSupertype, appView, namingLens);
}
// Rewrite nested classes.
- for (DexType nestedClass : nestedClasses) {
- if (appView.appInfo().isNonProgramTypeOrLiveProgramType(nestedClass)) {
- String descriptor =
- KotlinMetadataUtils.kotlinNameFromDescriptor(namingLens.lookupDescriptor(nestedClass));
+ for (KotlinTypeReference nestedClass : nestedClasses) {
+ String nestedDescriptor = nestedClass.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+ if (nestedDescriptor != null) {
// If the class is a nested class, it should be on the form Foo.Bar$Baz, where Baz is the
// name we should record.
- int innerClassIndex = descriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
- kmClass.visitNestedClass(descriptor.substring(innerClassIndex + 1));
+ int innerClassIndex = nestedDescriptor.lastIndexOf(DescriptorUtils.INNER_CLASS_SEPARATOR);
+ kmClass.visitNestedClass(nestedDescriptor.substring(innerClassIndex + 1));
}
}
// Rewrite sealed sub classes.
- for (DexType sealedSubClass : sealedSubClasses) {
- if (appView.appInfo().isNonProgramTypeOrLiveProgramType(sealedSubClass)) {
- String descriptor =
- KotlinMetadataUtils.kotlinNameFromDescriptor(
- namingLens.lookupDescriptor(sealedSubClass));
+ for (KotlinTypeReference sealedSubClass : sealedSubClasses) {
+ String sealedDescriptor =
+ sealedSubClass.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+ if (sealedDescriptor != null) {
kmClass.visitSealedSubclass(
- descriptor.replace(
+ sealedDescriptor.replace(
DescriptorUtils.INNER_CLASS_SEPARATOR, DescriptorUtils.JAVA_PACKAGE_SEPARATOR));
}
}
// TODO(b/154347404): Understand enum entries.
kmClass.getEnumEntries().addAll(enumEntries);
-
+ versionRequirements.rewrite(kmClass::visitVersionRequirement);
JvmExtensionsKt.setModuleName(kmClass, moduleName);
if (anonymousObjectOrigin != null) {
- JvmExtensionsKt.setAnonymousObjectOriginName(
- kmClass, KotlinMetadataUtils.kotlinNameFromDescriptor(anonymousObjectOrigin.descriptor));
+ String renamedAnon =
+ anonymousObjectOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+ if (renamedAnon != null) {
+ JvmExtensionsKt.setAnonymousObjectOriginName(kmClass, renamedAnon);
+ }
}
KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
@@ -279,4 +284,18 @@
public String getPackageName() {
return packageName;
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ forEachApply(constructorsWithNoBacking, constructor -> constructor::trace, definitionSupplier);
+ declarationContainerInfo.trace(definitionSupplier);
+ forEachApply(typeParameters, param -> param::trace, definitionSupplier);
+ forEachApply(superTypes, type -> type::trace, definitionSupplier);
+ forEachApply(sealedSubClasses, sealed -> sealed::trace, definitionSupplier);
+ forEachApply(nestedClasses, nested -> nested::trace, definitionSupplier);
+ // TODO(b/154347404): trace enum entries.
+ if (anonymousObjectOrigin != null) {
+ anonymousObjectOrigin.trace(definitionSupplier);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
index f9662ef..1a24001 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassLevelInfo.java
@@ -8,9 +8,14 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import kotlinx.metadata.jvm.KotlinClassHeader;
-public interface KotlinClassLevelInfo {
+public interface KotlinClassLevelInfo extends EnqueuerMetadataTraceable {
+
+ default boolean isNoKotlinInformation() {
+ return false;
+ }
default boolean isClass() {
return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 5fe02b2..2171a00 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueArray;
@@ -31,22 +32,18 @@
public static KotlinClassLevelInfo getKotlinInfo(
Kotlin kotlin,
DexClass clazz,
- DexDefinitionSupplier definitionSupplier,
+ DexItemFactory factory,
Reporter reporter,
boolean onlyProcessLambda,
Consumer<DexEncodedMethod> keepByteCode) {
- DexAnnotation meta =
- clazz
- .annotations()
- .getFirstMatching(definitionSupplier.dexItemFactory().kotlinMetadataType);
+ DexAnnotation meta = clazz.annotations().getFirstMatching(factory.kotlinMetadataType);
if (meta != null) {
try {
KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, meta.annotation);
if (onlyProcessLambda && kMetadata.getHeader().getKind() != KOTLIN_METADATA_KIND_LAMBDA) {
return NO_KOTLIN_INFO;
}
- return createKotlinInfo(
- kotlin, clazz, kMetadata, definitionSupplier, reporter, keepByteCode);
+ return createKotlinInfo(kotlin, clazz, kMetadata, factory, reporter, keepByteCode);
} catch (ClassCastException | InconsistentKotlinMetadataException | MetadataError e) {
reporter.info(
new StringDiagnostic(
@@ -68,6 +65,14 @@
return NO_KOTLIN_INFO;
}
+ public static boolean hasKotlinClassMetadataAnnotation(
+ DexClass clazz, DexDefinitionSupplier definitionSupplier) {
+ return clazz
+ .annotations()
+ .getFirstMatching(definitionSupplier.dexItemFactory().kotlinMetadataType)
+ != null;
+ }
+
public static KotlinClassMetadata toKotlinClassMetadata(
Kotlin kotlin, DexEncodedAnnotation metadataAnnotation) {
Map<DexString, DexAnnotationElement> elementMap = new IdentityHashMap<>();
@@ -103,7 +108,7 @@
Kotlin kotlin,
DexClass clazz,
KotlinClassMetadata kMetadata,
- DexDefinitionSupplier definitionSupplier,
+ DexItemFactory factory,
Reporter reporter,
Consumer<DexEncodedMethod> keepByteCode) {
String packageName = kMetadata.getHeader().getPackageName();
@@ -112,7 +117,7 @@
((KotlinClassMetadata.Class) kMetadata).toKmClass(),
packageName,
clazz,
- definitionSupplier,
+ factory,
reporter,
keepByteCode);
} else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
@@ -121,20 +126,20 @@
(KotlinClassMetadata.FileFacade) kMetadata,
packageName,
clazz,
- definitionSupplier,
+ factory,
reporter,
keepByteCode);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
// multi-file class with the same @JvmName.
return KotlinMultiFileClassFacadeInfo.create(
- (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, definitionSupplier);
+ (KotlinClassMetadata.MultiFileClassFacade) kMetadata, packageName, factory);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassPart) {
// A single file, which is part of multi-file class.
return KotlinMultiFileClassPartInfo.create(
(KotlinClassMetadata.MultiFileClassPart) kMetadata,
packageName,
clazz,
- definitionSupplier,
+ factory,
reporter,
keepByteCode);
} else if (kMetadata instanceof KotlinClassMetadata.SyntheticClass) {
@@ -143,7 +148,7 @@
packageName,
clazz,
kotlin,
- definitionSupplier,
+ factory,
reporter);
} else {
throw new MetadataError("unsupported 'k' value: " + kMetadata.getHeader().getKind());
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
index d6ff5b4..ac19e28 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -4,15 +4,13 @@
package com.android.tools.r8.kotlin;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromDescriptor;
-
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.Reporter;
import kotlinx.metadata.KmClassifier;
@@ -20,10 +18,10 @@
import kotlinx.metadata.KmClassifier.TypeParameter;
import kotlinx.metadata.KmTypeVisitor;
-public abstract class KotlinClassifierInfo {
+public abstract class KotlinClassifierInfo implements EnqueuerMetadataTraceable {
public static KotlinClassifierInfo create(
- KmClassifier classifier, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+ KmClassifier classifier, DexItemFactory factory, Reporter reporter) {
if (classifier instanceof KmClassifier.Class) {
String originalTypeName = ((KmClassifier.Class) classifier).getName();
// If this name starts with '.', it represents a local class or an anonymous object. This is
@@ -35,7 +33,7 @@
isLocalOrAnonymous ? originalTypeName.substring(1) : originalTypeName);
if (DescriptorUtils.isClassDescriptor(descriptor)) {
return new KotlinClassClassifierInfo(
- referenceTypeFromDescriptor(descriptor, definitionSupplier), isLocalOrAnonymous);
+ KotlinTypeReference.fromDescriptor(descriptor, factory), isLocalOrAnonymous);
} else {
return new KotlinUnknownClassClassifierInfo(originalTypeName);
}
@@ -54,10 +52,10 @@
public static class KotlinClassClassifierInfo extends KotlinClassifierInfo {
- private final DexType type;
+ private final KotlinTypeReference type;
private final boolean isLocalOrAnonymous;
- private KotlinClassClassifierInfo(DexType type, boolean isLocalOrAnonymous) {
+ private KotlinClassClassifierInfo(KotlinTypeReference type, boolean isLocalOrAnonymous) {
this.type = type;
this.isLocalOrAnonymous = isLocalOrAnonymous;
}
@@ -65,20 +63,21 @@
@Override
void rewrite(
KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
- if (appView.appInfo().wasPruned(type)) {
- visitor.visitClass(ClassClassifiers.anyName);
- return;
- }
- DexString descriptor = namingLens.lookupDescriptor(type);
+ String descriptor =
+ type.toRenamedDescriptorOrDefault(appView, namingLens, ClassClassifiers.anyDescriptor);
// For local or anonymous classes, the classifier is prefixed with '.' and inner classes are
// separated with '$'.
if (isLocalOrAnonymous) {
- visitor.visitClass(
- "." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor.toString()));
+ visitor.visitClass("." + DescriptorUtils.getBinaryNameFromDescriptor(descriptor));
} else {
- visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString()));
+ visitor.visitClass(DescriptorUtils.descriptorToKotlinClassifier(descriptor));
}
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ type.trace(definitionSupplier);
+ }
}
public static class KotlinTypeParameterClassifierInfo extends KotlinClassifierInfo {
@@ -94,6 +93,11 @@
KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
visitor.visitTypeParameter(typeId);
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ // Intentionally empty.
+ }
}
public static class KotlinTypeAliasClassifierInfo extends KotlinClassifierInfo {
@@ -109,6 +113,11 @@
KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
visitor.visitTypeAlias(typeAlias);
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ // Intentionally empty.
+ }
}
public static class KotlinUnknownClassClassifierInfo extends KotlinClassifierInfo {
@@ -123,6 +132,11 @@
KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
visitor.visitClass(classifier);
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ // Intentionally empty.
+ }
}
public static class KotlinUnknownClassifierInfo extends KotlinClassifierInfo {
@@ -137,5 +151,10 @@
KmTypeVisitor visitor, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
visitor.visitTypeAlias(classifier);
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ // Intentionally empty.
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
index 2f97b4f..1e19f1a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinCompanionInfo.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.kotlin;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.naming.NamingLens;
@@ -27,4 +28,9 @@
String finalName = dexString.toString();
visitor.visitCompanionObject(finalName);
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ // Do nothing.
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
index 6f9f366..c28b4a7 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinConstructorInfo.java
@@ -4,9 +4,12 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
@@ -21,27 +24,30 @@
// Information from original KmValueParameter(s) if available.
private final int flags;
// Information about the value parameters.
- private final List<KotlinValueParameterInfo> valueParameterInfos;
+ private final List<KotlinValueParameterInfo> valueParameters;
+ // Information about version requirements.
+ private final KotlinVersionRequirementInfo versionRequirements;
// Information about the signature.
private final KotlinJvmMethodSignatureInfo signature;
private KotlinConstructorInfo(
int flags,
- List<KotlinValueParameterInfo> valueParameterInfos,
+ List<KotlinValueParameterInfo> valueParameters,
+ KotlinVersionRequirementInfo versionRequirements,
KotlinJvmMethodSignatureInfo signature) {
this.flags = flags;
- this.valueParameterInfos = valueParameterInfos;
+ this.valueParameters = valueParameters;
+ this.versionRequirements = versionRequirements;
this.signature = signature;
}
public static KotlinConstructorInfo create(
- KmConstructor kmConstructor, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+ KmConstructor kmConstructor, DexItemFactory factory, Reporter reporter) {
return new KotlinConstructorInfo(
kmConstructor.getFlags(),
- KotlinValueParameterInfo.create(
- kmConstructor.getValueParameters(), definitionSupplier, reporter),
- KotlinJvmMethodSignatureInfo.create(
- JvmExtensionsKt.getSignature(kmConstructor), definitionSupplier));
+ KotlinValueParameterInfo.create(kmConstructor.getValueParameters(), factory, reporter),
+ KotlinVersionRequirementInfo.create(kmConstructor.getVersionRequirements()),
+ KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmConstructor), factory));
}
public void rewrite(
@@ -56,9 +62,10 @@
if (signature != null) {
JvmExtensionsKt.setSignature(kmConstructor, signature.rewrite(method, appView, namingLens));
}
- for (KotlinValueParameterInfo valueParameterInfo : valueParameterInfos) {
+ for (KotlinValueParameterInfo valueParameterInfo : valueParameters) {
valueParameterInfo.rewrite(kmConstructor::visitValueParameter, appView, namingLens);
}
+ versionRequirements.rewrite(kmConstructor::visitVersionRequirement);
kmClass.getConstructors().add(kmConstructor);
}
@@ -71,4 +78,12 @@
public KotlinConstructorInfo asConstructor() {
return this;
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ forEachApply(valueParameters, param -> param::trace, definitionSupplier);
+ if (signature != null) {
+ signature.trace(definitionSupplier);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index cd349ab..a83f77a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -5,15 +5,18 @@
package com.android.tools.r8.kotlin;
import static com.android.tools.r8.kotlin.KotlinMetadataUtils.isValidMethodDescriptor;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
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.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.kotlin.KotlinMetadataUtils.KmPropertyProcessor;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
import java.util.IdentityHashMap;
@@ -29,7 +32,7 @@
import kotlinx.metadata.jvm.JvmMethodSignature;
// Holds information about KmDeclarationContainer
-public class KotlinDeclarationContainerInfo {
+public class KotlinDeclarationContainerInfo implements EnqueuerMetadataTraceable {
private final List<KotlinTypeAliasInfo> typeAliases;
// The functions in notBackedFunctions are KmFunctions where we could not find a representative.
@@ -51,7 +54,7 @@
KmDeclarationContainer container,
Map<String, DexEncodedMethod> methodSignatureMap,
Map<String, DexEncodedField> fieldSignatureMap,
- DexDefinitionSupplier definitionSupplier,
+ DexItemFactory factory,
Reporter reporter,
Consumer<DexEncodedMethod> keepByteCode) {
ImmutableList.Builder<KotlinFunctionInfo> notBackedFunctions = ImmutableList.builder();
@@ -62,7 +65,7 @@
continue;
}
KotlinFunctionInfo kotlinFunctionInfo =
- KotlinFunctionInfo.create(kmFunction, definitionSupplier, reporter);
+ KotlinFunctionInfo.create(kmFunction, factory, reporter);
DexEncodedMethod method = methodSignatureMap.get(signature.asString());
if (method == null) {
notBackedFunctions.add(kotlinFunctionInfo);
@@ -85,7 +88,7 @@
ImmutableList.Builder<KotlinPropertyInfo> notBackedProperties = ImmutableList.builder();
for (KmProperty kmProperty : container.getProperties()) {
KotlinPropertyInfo kotlinPropertyInfo =
- KotlinPropertyInfo.create(kmProperty, definitionSupplier, reporter);
+ KotlinPropertyInfo.create(kmProperty, factory, reporter);
KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty);
boolean hasBacking = false;
if (propertyProcessor.fieldSignature() != null) {
@@ -119,7 +122,7 @@
}
}
return new KotlinDeclarationContainerInfo(
- getTypeAliases(container.getTypeAliases(), definitionSupplier, reporter),
+ getTypeAliases(container.getTypeAliases(), factory, reporter),
notBackedFunctions.build(),
notBackedProperties.build());
}
@@ -139,10 +142,10 @@
}
private static List<KotlinTypeAliasInfo> getTypeAliases(
- List<KmTypeAlias> aliases, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+ List<KmTypeAlias> aliases, DexItemFactory factory, Reporter reporter) {
ImmutableList.Builder<KotlinTypeAliasInfo> builder = ImmutableList.builder();
for (KmTypeAlias alias : aliases) {
- builder.add(KotlinTypeAliasInfo.create(alias, definitionSupplier, reporter));
+ builder.add(KotlinTypeAliasInfo.create(alias, factory, reporter));
}
return builder.build();
}
@@ -208,6 +211,13 @@
}
}
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ forEachApply(typeAliases, alias -> alias::trace, definitionSupplier);
+ forEachApply(functionsWithNoBacking, function -> function::trace, definitionSupplier);
+ forEachApply(propertiesWithNoBacking, property -> property::trace, definitionSupplier);
+ }
+
public static class KotlinPropertyGroup {
private DexEncodedField backingField = null;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
index 7df7afb..8d9d468 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFieldLevelInfo.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.kotlin;
-public interface KotlinFieldLevelInfo {
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+
+public interface KotlinFieldLevelInfo extends EnqueuerMetadataTraceable {
default boolean isCompanion() {
return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
index c31df8c..94617e1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFileFacadeInfo.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
@@ -32,12 +33,12 @@
FileFacade kmFileFacade,
String packageName,
DexClass clazz,
- DexDefinitionSupplier definitionSupplier,
+ DexItemFactory factory,
Reporter reporter,
Consumer<DexEncodedMethod> keepByteCode) {
return new KotlinFileFacadeInfo(
KotlinPackageInfo.create(
- kmFileFacade.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode),
+ kmFileFacade.toKmPackage(), clazz, factory, reporter, keepByteCode),
packageName);
}
@@ -65,4 +66,9 @@
public String getPackageName() {
return packageName;
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ packageInfo.trace(definitionSupplier);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
index c8c8836..da2e960 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFlexibleTypeUpperBoundInfo.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
@@ -45,22 +46,20 @@
}
static KotlinFlexibleTypeUpperBoundInfo create(
- KmFlexibleTypeUpperBound flexibleTypeUpperBound,
- DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ KmFlexibleTypeUpperBound flexibleTypeUpperBound, DexItemFactory factory, Reporter reporter) {
if (flexibleTypeUpperBound == null) {
return NO_FLEXIBLE_UPPER_BOUND;
}
KmType kmType = flexibleTypeUpperBound.getType();
return new KotlinFlexibleTypeUpperBoundInfo(
kmType.getFlags(),
- KotlinClassifierInfo.create(kmType.classifier, definitionSupplier, reporter),
- KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter),
- KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter),
- getArguments(kmType.getArguments(), definitionSupplier, reporter),
- KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier),
+ KotlinClassifierInfo.create(kmType.classifier, factory, reporter),
+ KotlinTypeInfo.create(kmType.getAbbreviatedType(), factory, reporter),
+ KotlinTypeInfo.create(kmType.getOuterType(), factory, reporter),
+ getArguments(kmType.getArguments(), factory, reporter),
+ KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), factory),
KotlinFlexibleTypeUpperBoundInfo.create(
- kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter),
+ kmType.getFlexibleTypeUpperBound(), factory, reporter),
flexibleTypeUpperBound.getTypeFlexibilityId());
}
@@ -74,4 +73,12 @@
}
super.rewrite(flags -> visitorProvider.get(flags, typeFlexibilityId), appView, namingLens);
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ if (this == NO_FLEXIBLE_UPPER_BOUND) {
+ return;
+ }
+ super.trace(definitionSupplier);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index 58b07cb..d71d574 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -4,12 +4,12 @@
package com.android.tools.r8.kotlin;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromBinaryName;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
@@ -36,7 +36,11 @@
// Information about the signature
private final KotlinJvmMethodSignatureInfo signature;
// Information about the lambdaClassOrigin.
- private final DexType lambdaClassOrigin;
+ private final KotlinTypeReference lambdaClassOrigin;
+ // Information about version requirements.
+ private final KotlinVersionRequirementInfo versionRequirements;
+ // A value describing if any of the parameters are crossinline.
+ private final boolean crossInlineParameter;
private KotlinFunctionInfo(
int flags,
@@ -46,7 +50,9 @@
List<KotlinValueParameterInfo> valueParameters,
List<KotlinTypeParameterInfo> typeParameters,
KotlinJvmMethodSignatureInfo signature,
- DexType lambdaClassOrigin) {
+ KotlinTypeReference lambdaClassOrigin,
+ KotlinVersionRequirementInfo versionRequirements,
+ boolean crossInlineParameter) {
this.flags = flags;
this.name = name;
this.returnType = returnType;
@@ -55,29 +61,43 @@
this.typeParameters = typeParameters;
this.signature = signature;
this.lambdaClassOrigin = lambdaClassOrigin;
+ this.versionRequirements = versionRequirements;
+ this.crossInlineParameter = crossInlineParameter;
+ }
+
+ public boolean hasCrossInlineParameter() {
+ return crossInlineParameter;
}
static KotlinFunctionInfo create(
- KmFunction kmFunction, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+ KmFunction kmFunction, DexItemFactory factory, Reporter reporter) {
+ boolean isCrossInline = false;
+ List<KotlinValueParameterInfo> valueParameters =
+ KotlinValueParameterInfo.create(kmFunction.getValueParameters(), factory, reporter);
+ for (KotlinValueParameterInfo valueParameter : valueParameters) {
+ if (valueParameter.isCrossInline()) {
+ isCrossInline = true;
+ break;
+ }
+ }
return new KotlinFunctionInfo(
kmFunction.getFlags(),
kmFunction.getName(),
- KotlinTypeInfo.create(kmFunction.getReturnType(), definitionSupplier, reporter),
- KotlinTypeInfo.create(kmFunction.getReceiverParameterType(), definitionSupplier, reporter),
- KotlinValueParameterInfo.create(
- kmFunction.getValueParameters(), definitionSupplier, reporter),
- KotlinTypeParameterInfo.create(
- kmFunction.getTypeParameters(), definitionSupplier, reporter),
- KotlinJvmMethodSignatureInfo.create(
- JvmExtensionsKt.getSignature(kmFunction), definitionSupplier),
- getlambdaClassOrigin(kmFunction, definitionSupplier));
+ KotlinTypeInfo.create(kmFunction.getReturnType(), factory, reporter),
+ KotlinTypeInfo.create(kmFunction.getReceiverParameterType(), factory, reporter),
+ valueParameters,
+ KotlinTypeParameterInfo.create(kmFunction.getTypeParameters(), factory, reporter),
+ KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmFunction), factory),
+ getlambdaClassOrigin(kmFunction, factory),
+ KotlinVersionRequirementInfo.create(kmFunction.getVersionRequirements()),
+ isCrossInline);
}
- private static DexType getlambdaClassOrigin(
- KmFunction kmFunction, DexDefinitionSupplier definitionSupplier) {
+ private static KotlinTypeReference getlambdaClassOrigin(
+ KmFunction kmFunction, DexItemFactory factory) {
String lambdaClassOriginName = JvmExtensionsKt.getLambdaClassOriginName(kmFunction);
if (lambdaClassOriginName != null) {
- return referenceTypeFromBinaryName(lambdaClassOriginName, definitionSupplier);
+ return KotlinTypeReference.fromBinaryName(lambdaClassOriginName, factory);
}
return null;
}
@@ -108,14 +128,18 @@
if (receiverParameterType != null) {
receiverParameterType.rewrite(kmFunction::visitReceiverParameterType, appView, namingLens);
}
+ versionRequirements.rewrite(kmFunction::visitVersionRequirement);
JvmFunctionExtensionVisitor extensionVisitor =
(JvmFunctionExtensionVisitor) kmFunction.visitExtensions(JvmFunctionExtensionVisitor.TYPE);
if (signature != null && extensionVisitor != null) {
extensionVisitor.visit(signature.rewrite(method, appView, namingLens));
}
if (lambdaClassOrigin != null && extensionVisitor != null) {
- extensionVisitor.visitLambdaClassOriginName(
- KotlinMetadataUtils.kotlinNameFromDescriptor(lambdaClassOrigin.descriptor));
+ String lambdaClassOriginName =
+ lambdaClassOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+ if (lambdaClassOriginName != null) {
+ extensionVisitor.visitLambdaClassOriginName(lambdaClassOriginName);
+ }
}
}
@@ -132,4 +156,24 @@
public boolean isExtensionFunction() {
return receiverParameterType != null;
}
+
+ public KotlinJvmMethodSignatureInfo getSignature() {
+ return signature;
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ forEachApply(valueParameters, param -> param::trace, definitionSupplier);
+ returnType.trace(definitionSupplier);
+ if (receiverParameterType != null) {
+ receiverParameterType.trace(definitionSupplier);
+ }
+ forEachApply(typeParameters, param -> param::trace, definitionSupplier);
+ if (signature != null) {
+ signature.trace(definitionSupplier);
+ }
+ if (lambdaClassOrigin != null) {
+ lambdaClassOrigin.trace(definitionSupplier);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
index 69c0442..6daf3d7 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmFieldSignatureInfo.java
@@ -4,39 +4,37 @@
package com.android.tools.r8.kotlin;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromDescriptor;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toRenamedDescriptorOrDefault;
-
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import kotlinx.metadata.jvm.JvmFieldSignature;
/**
* The JvmSignature for a method or property does not always correspond to the actual signature, see
* b/154201250. We therefore need to model the signature as well.
*/
-public class KotlinJvmFieldSignatureInfo {
+public class KotlinJvmFieldSignatureInfo implements EnqueuerMetadataTraceable {
- private final DexType type;
+ private final KotlinTypeReference type;
private final String name;
- private KotlinJvmFieldSignatureInfo(String name, DexType type) {
+ private KotlinJvmFieldSignatureInfo(String name, KotlinTypeReference type) {
this.name = name;
this.type = type;
}
public static KotlinJvmFieldSignatureInfo create(
- JvmFieldSignature fieldSignature, DexDefinitionSupplier definitionSupplier) {
+ JvmFieldSignature fieldSignature, DexItemFactory factory) {
if (fieldSignature == null) {
return null;
}
return new KotlinJvmFieldSignatureInfo(
fieldSignature.getName(),
- referenceTypeFromDescriptor(fieldSignature.getDesc(), definitionSupplier));
+ KotlinTypeReference.fromDescriptor(fieldSignature.getDesc(), factory));
}
public JvmFieldSignature rewrite(
@@ -51,6 +49,11 @@
}
String defValue = appView.dexItemFactory().objectType.toDescriptorString();
return new JvmFieldSignature(
- finalName, toRenamedDescriptorOrDefault(type, appView, namingLens, defValue));
+ finalName, type.toRenamedDescriptorOrDefault(appView, namingLens, defValue));
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ type.trace(definitionSupplier);
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
index d9d5c9d..f4674a1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinJvmMethodSignatureInfo.java
@@ -4,15 +4,15 @@
package com.android.tools.r8.kotlin;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.referenceTypeFromDescriptor;
-import static com.android.tools.r8.kotlin.KotlinMetadataUtils.toRenamedDescriptorOrDefault;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -22,36 +22,51 @@
* The JvmSignature for a method or property does not always correspond to the actual signature, see
* b/154201250. We therefore need to model the signature as well.
*/
-public class KotlinJvmMethodSignatureInfo {
+public class KotlinJvmMethodSignatureInfo implements EnqueuerMetadataTraceable {
- private static final List<DexType> EMPTY_PARAMETERS_LIST = ImmutableList.of();
+ private static final List<KotlinTypeReference> EMPTY_PARAMETERS_LIST = ImmutableList.of();
private final String name;
- private final DexType returnType;
- private final List<DexType> parameters;
+ private final KotlinTypeReference returnType;
+ private final List<KotlinTypeReference> parameters;
+ private final String invalidDescriptor;
- private KotlinJvmMethodSignatureInfo(String name, DexType returnType, List<DexType> parameters) {
+ private KotlinJvmMethodSignatureInfo(
+ String name, KotlinTypeReference returnType, List<KotlinTypeReference> parameters) {
this.name = name;
this.returnType = returnType;
this.parameters = parameters;
+ this.invalidDescriptor = null;
+ }
+
+ private KotlinJvmMethodSignatureInfo(String name, String invalidDescriptor) {
+ this.name = name;
+ this.invalidDescriptor = invalidDescriptor;
+ this.parameters = EMPTY_PARAMETERS_LIST;
+ this.returnType = null;
}
public static KotlinJvmMethodSignatureInfo create(
- JvmMethodSignature methodSignature, DexDefinitionSupplier definitionSupplier) {
+ JvmMethodSignature methodSignature, DexItemFactory factory) {
if (methodSignature == null) {
return null;
}
String kotlinDescriptor = methodSignature.getDesc();
+ if (!KotlinMetadataUtils.isValidMethodDescriptor(kotlinDescriptor)) {
+ // If the method descriptor is invalid, keep it as invalid.
+ return new KotlinJvmMethodSignatureInfo(methodSignature.getName(), kotlinDescriptor);
+ }
String returnTypeDescriptor = DescriptorUtils.getReturnTypeDescriptor(kotlinDescriptor);
- DexType returnType = referenceTypeFromDescriptor(returnTypeDescriptor, definitionSupplier);
+ KotlinTypeReference returnType =
+ KotlinTypeReference.fromDescriptor(returnTypeDescriptor, factory);
String[] descriptors = DescriptorUtils.getArgumentTypeDescriptors(kotlinDescriptor);
if (descriptors.length == 0) {
return new KotlinJvmMethodSignatureInfo(
methodSignature.getName(), returnType, EMPTY_PARAMETERS_LIST);
}
- ImmutableList.Builder<DexType> parameters = ImmutableList.builder();
+ ImmutableList.Builder<KotlinTypeReference> parameters = ImmutableList.builder();
for (String descriptor : descriptors) {
- parameters.add(referenceTypeFromDescriptor(descriptor, definitionSupplier));
+ parameters.add(KotlinTypeReference.fromDescriptor(descriptor, factory));
}
return new KotlinJvmMethodSignatureInfo(
methodSignature.getName(), returnType, parameters.build());
@@ -59,6 +74,10 @@
public JvmMethodSignature rewrite(
DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+ if (invalidDescriptor != null) {
+ return new JvmMethodSignature(name, invalidDescriptor);
+ }
+ assert returnType != null;
String finalName = name;
if (method != null) {
String methodName = method.method.name.toString();
@@ -70,11 +89,38 @@
StringBuilder descBuilder = new StringBuilder();
descBuilder.append("(");
String defValue = appView.dexItemFactory().objectType.toDescriptorString();
- for (DexType parameter : parameters) {
- descBuilder.append(toRenamedDescriptorOrDefault(parameter, appView, namingLens, defValue));
+ for (KotlinTypeReference parameter : parameters) {
+ descBuilder.append(parameter.toRenamedDescriptorOrDefault(appView, namingLens, defValue));
}
descBuilder.append(")");
- descBuilder.append(toRenamedDescriptorOrDefault(returnType, appView, namingLens, defValue));
+ descBuilder.append(returnType.toRenamedDescriptorOrDefault(appView, namingLens, defValue));
return new JvmMethodSignature(finalName, descBuilder.toString());
}
+
+ @Override
+ public String toString() {
+ if (invalidDescriptor != null) {
+ return name + "(" + invalidDescriptor + ")";
+ }
+ assert returnType != null;
+ StringBuilder descBuilder = new StringBuilder();
+ descBuilder.append(name);
+ descBuilder.append("(");
+ for (KotlinTypeReference parameter : parameters) {
+ descBuilder.append(parameter.toString());
+ }
+ descBuilder.append(")");
+ descBuilder.append(returnType.toString());
+ return descBuilder.toString();
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ if (invalidDescriptor != null) {
+ return;
+ }
+ assert returnType != null;
+ returnType.trace(definitionSupplier);
+ forEachApply(parameters, param -> param::trace, definitionSupplier);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
index d25f105..459599b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLambdaInfo.java
@@ -10,48 +10,44 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Reporter;
import kotlinx.metadata.KmLambda;
-import kotlinx.metadata.KmLambdaVisitor;
import kotlinx.metadata.jvm.JvmExtensionsKt;
import kotlinx.metadata.jvm.JvmMethodSignature;
// Holds information about a KmLambda
-public class KotlinLambdaInfo {
+public class KotlinLambdaInfo implements EnqueuerMetadataTraceable {
private final KotlinFunctionInfo function;
+ private final boolean hasBacking;
- private KotlinLambdaInfo(KotlinFunctionInfo function) {
+ private KotlinLambdaInfo(KotlinFunctionInfo function, boolean hasBacking) {
this.function = function;
+ this.hasBacking = hasBacking;
}
static KotlinLambdaInfo create(
- DexClass clazz,
- KmLambda lambda,
- DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ DexClass clazz, KmLambda lambda, DexItemFactory factory, Reporter reporter) {
if (lambda == null) {
assert false;
return null;
}
+ KotlinFunctionInfo kotlinFunctionInfo =
+ KotlinFunctionInfo.create(lambda.function, factory, reporter);
JvmMethodSignature signature = JvmExtensionsKt.getSignature(lambda.function);
- if (signature == null) {
- assert false;
- return null;
- }
- for (DexEncodedMethod method : clazz.methods()) {
- if (toJvmMethodSignature(method.method).asString().equals(signature.asString())) {
- KotlinFunctionInfo kotlinFunctionInfo =
- KotlinFunctionInfo.create(lambda.function, definitionSupplier, reporter);
- method.setKotlinMemberInfo(kotlinFunctionInfo);
- return new KotlinLambdaInfo(kotlinFunctionInfo);
+ if (signature != null) {
+ for (DexEncodedMethod method : clazz.methods()) {
+ if (toJvmMethodSignature(method.method).asString().equals(signature.asString())) {
+ method.setKotlinMemberInfo(kotlinFunctionInfo);
+ return new KotlinLambdaInfo(kotlinFunctionInfo, true);
+ }
}
}
- // TODO(b/155536535): Resolve this assert for NestTreeShakeJarVerificationTest.
- // assert false;
- return null;
+ return new KotlinLambdaInfo(kotlinFunctionInfo, false);
}
boolean rewrite(
@@ -59,13 +55,31 @@
DexClass clazz,
AppView<AppInfoWithLiveness> appView,
NamingLens namingLens) {
+ if (!hasBacking) {
+ function.rewrite(visitorProvider.get()::visitFunction, null, appView, namingLens);
+ return true;
+ }
+ DexEncodedMethod backing = null;
for (DexEncodedMethod method : clazz.methods()) {
if (method.getKotlinMemberInfo() == function) {
- KmLambdaVisitor kmLambdaVisitor = visitorProvider.get();
- function.rewrite(kmLambdaVisitor::visitFunction, method, appView, namingLens);
- return true;
+ backing = method;
+ break;
}
}
- return false;
+ if (backing == null) {
+ appView
+ .options()
+ .reporter
+ .info(
+ KotlinMetadataDiagnostic.lambdaBackingNotFound(clazz.type, function.getSignature()));
+ return false;
+ }
+ function.rewrite(visitorProvider.get()::visitFunction, backing, appView, namingLens);
+ return true;
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ function.trace(definitionSupplier);
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
index 83947d7..3415e13 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
@@ -73,4 +73,18 @@
+ StringUtils.LINE_SEPARATOR
+ StringUtils.stacktraceAsString(t));
}
+
+ static KotlinMetadataDiagnostic lambdaBackingNotFound(
+ DexType type, KotlinJvmMethodSignatureInfo signatureInfo) {
+ return new KotlinMetadataDiagnostic(
+ Origin.unknown(),
+ Position.UNKNOWN,
+ "The lambda function "
+ + signatureInfo.toString()
+ + " could no longer be found in "
+ + type.toSourceString()
+ + " . The method is most likely pruned and would require a specific keep rule to keep"
+ + " alive. As a result, the metadata information regarding the lambda structure has"
+ + " been discarded.");
+ }
}
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 28928ef..ba814e9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -4,11 +4,20 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinClassMetadataReader.hasKotlinClassMetadataAnnotation;
+import static com.android.tools.r8.kotlin.KotlinMetadataUtils.NO_KOTLIN_INFO;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.errors.Unreachable;
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.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
import com.android.tools.r8.shaking.Enqueuer;
import com.google.common.collect.Sets;
@@ -18,12 +27,11 @@
private final AppView<?> appView;
private final DexDefinitionSupplier definitionSupplier;
- private final Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet();
public KotlinMetadataEnqueuerExtension(
- AppView<?> appView, DexDefinitionSupplier definitionSupplier) {
+ AppView<?> appView, DexDefinitionSupplier definitionSupplier, Set<DexType> prunedTypes) {
this.appView = appView;
- this.definitionSupplier = definitionSupplier;
+ this.definitionSupplier = new KotlinMetadataDefinitionSupplier(definitionSupplier, prunedTypes);
}
@Override
@@ -31,23 +39,111 @@
DexType kotlinMetadataType = appView.dexItemFactory().kotlinMetadataType;
DexClass kotlinMetadataClass =
appView.appInfo().definitionForWithoutExistenceAssert(kotlinMetadataType);
- // We will process kotlin.Metadata even if the type is not present in the program, as long as
- // the annotation will be in the output
+ // In the first round of tree shaking build up all metadata such that it can be traced later.
boolean keepMetadata =
- enqueuer.isPinned(kotlinMetadataType)
- || enqueuer.isMissing(kotlinMetadataType)
- || (kotlinMetadataClass != null && kotlinMetadataClass.isNotProgramClass());
+ kotlinMetadataClass == null
+ || kotlinMetadataClass.isNotProgramClass()
+ || enqueuer.isPinned(kotlinMetadataType);
+ if (enqueuer.getMode().isInitialTreeShaking()) {
+ Set<DexMethod> keepByteCodeFunctions = Sets.newIdentityHashSet();
+ Set<DexProgramClass> localOrAnonymousClasses = Sets.newIdentityHashSet();
+ enqueuer.forAllLiveClasses(
+ clazz -> {
+ boolean onlyProcessLambdas = !keepMetadata || !enqueuer.isPinned(clazz.type);
+ assert clazz.getKotlinInfo().isNoKotlinInformation();
+ clazz.setKotlinInfo(
+ KotlinClassMetadataReader.getKotlinInfo(
+ appView.dexItemFactory().kotlin,
+ clazz,
+ definitionSupplier.dexItemFactory(),
+ appView.options().reporter,
+ onlyProcessLambdas,
+ method -> keepByteCodeFunctions.add(method.method)));
+ if (clazz.getEnclosingMethodAttribute() != null
+ && clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
+ localOrAnonymousClasses.add(clazz);
+ }
+ });
+ appView.setCfByteCodePassThrough(keepByteCodeFunctions);
+ for (DexProgramClass localOrAnonymousClass : localOrAnonymousClasses) {
+ EnclosingMethodAttribute enclosingAttribute =
+ localOrAnonymousClass.getEnclosingMethodAttribute();
+ DexClass holder =
+ definitionSupplier.definitionForHolder(enclosingAttribute.getEnclosingMethod());
+ if (holder == null) {
+ continue;
+ }
+ DexEncodedMethod method = holder.lookupMethod(enclosingAttribute.getEnclosingMethod());
+ // If we cannot lookup the method, the conservative choice is keep the byte code.
+ if (method == null
+ || (method.getKotlinMemberInfo().isFunction()
+ && method.getKotlinMemberInfo().asFunction().hasCrossInlineParameter())) {
+ localOrAnonymousClass.forEachProgramMethod(
+ m -> keepByteCodeFunctions.add(m.getReference()));
+ }
+ }
+ } else {
+ assert verifyKotlinMetadataModeledForAllClasses(enqueuer, keepMetadata);
+ }
+ // Trace through the modeled kotlin metadata.
enqueuer.forAllLiveClasses(
clazz -> {
- clazz.setKotlinInfo(
- KotlinClassMetadataReader.getKotlinInfo(
- appView.dexItemFactory().kotlin,
- clazz,
- definitionSupplier,
- appView.options().reporter,
- !keepMetadata || !enqueuer.isPinned(clazz.type),
- method -> keepByteCodeFunctions.add(method.method)));
+ clazz.getKotlinInfo().trace(definitionSupplier);
+ forEachApply(
+ clazz.methods(), method -> method.getKotlinMemberInfo()::trace, definitionSupplier);
+ forEachApply(
+ clazz.fields(), field -> field.getKotlinMemberInfo()::trace, definitionSupplier);
});
- appView.setCfByteCodePassThrough(keepByteCodeFunctions);
+ }
+
+ private boolean verifyKotlinMetadataModeledForAllClasses(
+ Enqueuer enqueuer, boolean keepMetadata) {
+ enqueuer.forAllLiveClasses(
+ clazz -> {
+ // Trace through class and member definitions
+ assert !hasKotlinClassMetadataAnnotation(clazz, definitionSupplier)
+ || !keepMetadata
+ || !enqueuer.isPinned(clazz.type)
+ || clazz.getKotlinInfo() != NO_KOTLIN_INFO;
+ });
+ return true;
+ }
+
+ public static class KotlinMetadataDefinitionSupplier implements DexDefinitionSupplier {
+
+ private final DexDefinitionSupplier baseSupplier;
+ private final Set<DexType> prunedTypes;
+
+ private KotlinMetadataDefinitionSupplier(
+ DexDefinitionSupplier baseSupplier, Set<DexType> prunedTypes) {
+ this.baseSupplier = baseSupplier;
+ this.prunedTypes = prunedTypes;
+ }
+
+ @Override
+ public DexEncodedMethod definitionFor(DexMethod method) {
+ throw new Unreachable("Should not be called");
+ }
+
+ @Override
+ public DexClass definitionFor(DexType type) {
+ // TODO(b/157700128) Metadata cannot at this point keep anything alive. Therefore, if a type
+ // has been pruned it may still be referenced, so we do an early check here to ensure it will
+ // not end up as. Ideally, those types should be removed by a pass on the modeled data.
+ if (prunedTypes != null && prunedTypes.contains(type)) {
+ return null;
+ }
+ return baseSupplier.definitionFor(type);
+ }
+
+ @Override
+ public DexProgramClass definitionForProgramType(DexType type) {
+ throw new Unreachable("Should not be called");
+ }
+
+ @Override
+ public DexItemFactory dexItemFactory() {
+ return baseSupplier.dexItemFactory();
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index 1f21451..1ee78a5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -59,6 +58,16 @@
public String getPackageName() {
throw new Unreachable("Should never be called");
}
+
+ @Override
+ public boolean isNoKotlinInformation() {
+ return true;
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ // No information needed to trace.
+ }
}
static JvmFieldSignature toJvmFieldSignature(DexField field) {
@@ -137,41 +146,6 @@
}
}
- static String toRenamedDescriptorOrDefault(
- DexType type,
- AppView<AppInfoWithLiveness> appView,
- NamingLens namingLens,
- String defaultValue) {
- if (appView.appInfo().wasPruned(type)) {
- return defaultValue;
- }
- DexString descriptor = namingLens.lookupDescriptor(type);
- if (descriptor != null) {
- return descriptor.toString();
- }
- return defaultValue;
- }
-
- static String kotlinNameFromDescriptor(DexString descriptor) {
- return DescriptorUtils.getBinaryNameFromDescriptor(descriptor.toString());
- }
-
- static DexType referenceTypeFromBinaryName(
- String binaryName, DexDefinitionSupplier definitionSupplier) {
- return referenceTypeFromDescriptor(
- DescriptorUtils.getDescriptorFromClassBinaryName(binaryName), definitionSupplier);
- }
-
- static DexType referenceTypeFromDescriptor(
- String descriptor, DexDefinitionSupplier definitionSupplier) {
- DexType type = definitionSupplier.dexItemFactory().createType(descriptor);
- // Lookup the definition, ignoring the result. This populates the sets in the Enqueuer.
- if (type.isClassType()) {
- definitionSupplier.definitionFor(type);
- }
- return type;
- }
-
public static boolean mayProcessKotlinMetadata(AppView<?> appView) {
// This can run before we have determined the pinned items, because we may need to load the
// stack-map table on input. This is therefore a conservative guess on kotlin.Metadata is kept.
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index fa21f81..bc124c6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -33,6 +33,7 @@
import kotlinx.metadata.KmTypeParameter;
import kotlinx.metadata.KmTypeProjection;
import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.KmVersionRequirement;
import kotlinx.metadata.jvm.JvmExtensionsKt;
import kotlinx.metadata.jvm.JvmFieldSignature;
import kotlinx.metadata.jvm.JvmMethodSignature;
@@ -351,6 +352,7 @@
appendKmProperty(nextNextIndent, sb, kmProperty);
});
});
+ appendKmVersionRequirement(indent, sb, kmClass.getVersionRequirements());
appendKeyValue(
indent,
"constructors",
@@ -389,6 +391,7 @@
JvmMethodSignature signature = JvmExtensionsKt.getSignature(constructor);
appendKeyValue(
newIndent, "signature", sb, signature != null ? signature.asString() : "null");
+ appendKmVersionRequirement(newIndent, sb, constructor.getVersionRequirements());
});
}
@@ -420,6 +423,7 @@
"valueParameters",
sb,
nextIndent -> appendValueParameters(nextIndent, sb, function.getValueParameters()));
+ appendKmVersionRequirement(newIndent, sb, function.getVersionRequirements());
JvmMethodSignature signature = JvmExtensionsKt.getSignature(function);
appendKeyValue(
newIndent, "signature", sb, signature != null ? signature.asString() : "null");
@@ -461,6 +465,7 @@
"setterParameter",
sb,
nextIndent -> appendValueParameter(nextIndent, sb, kmProperty.getSetterParameter()));
+ appendKmVersionRequirement(newIndent, sb, kmProperty.getVersionRequirements());
appendKeyValue(newIndent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmProperty) + "");
JvmFieldSignature fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
appendKeyValue(
@@ -730,6 +735,7 @@
nextIndent -> {
appendKmType(nextIndent, sb, kmTypeAlias.underlyingType);
});
+ appendKmVersionRequirement(newIndent, sb, kmTypeAlias.getVersionRequirements());
});
}
@@ -753,4 +759,44 @@
});
});
}
+
+ private static void appendKmVersionRequirement(
+ String indent, StringBuilder sb, List<KmVersionRequirement> kmVersionRequirements) {
+ appendKeyValue(
+ indent,
+ "versionRequirements",
+ sb,
+ newIndent -> {
+ appendKmList(
+ newIndent,
+ "KmVersionRequirement",
+ sb,
+ kmVersionRequirements,
+ (nextIndent, kmVersionRequirement) -> {
+ appendKmSection(
+ nextIndent,
+ "KmVersionRequirement",
+ sb,
+ nextNextIndent -> {
+ appendKeyValue(nextNextIndent, "kind", sb, kmVersionRequirement.kind.name());
+ appendKeyValue(
+ nextNextIndent, "level", sb, kmVersionRequirement.level.name());
+ appendKeyValue(
+ nextNextIndent,
+ "errorCode",
+ sb,
+ kmVersionRequirement.getErrorCode() == null
+ ? "null"
+ : kmVersionRequirement.getErrorCode().toString());
+ appendKeyValue(
+ nextNextIndent, "message", sb, kmVersionRequirement.getMessage());
+ appendKeyValue(
+ nextNextIndent,
+ "version",
+ sb,
+ kmVersionRequirement.getVersion().toString());
+ });
+ });
+ });
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
index 604374e..a02cb0a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMethodLevelInfo.java
@@ -4,7 +4,9 @@
package com.android.tools.r8.kotlin;
-public interface KotlinMethodLevelInfo {
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+
+public interface KotlinMethodLevelInfo extends EnqueuerMetadataTraceable {
default boolean isConstructor() {
return false;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
index 3baea9c..1ccd35f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassFacadeInfo.java
@@ -4,14 +4,14 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
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.DexString;
-import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
@@ -22,23 +22,20 @@
// Holds information about Metadata.MultiFileClassFace
public class KotlinMultiFileClassFacadeInfo implements KotlinClassLevelInfo {
- private final List<DexType> partClassNames;
+ private final List<KotlinTypeReference> partClassNames;
private final String packageName;
- private KotlinMultiFileClassFacadeInfo(List<DexType> partClassNames, String packageName) {
+ private KotlinMultiFileClassFacadeInfo(
+ List<KotlinTypeReference> partClassNames, String packageName) {
this.partClassNames = partClassNames;
this.packageName = packageName;
}
static KotlinMultiFileClassFacadeInfo create(
- MultiFileClassFacade kmMultiFileClassFacade,
- String packageName,
- DexDefinitionSupplier definitionSupplier) {
- ImmutableList.Builder<DexType> builder = ImmutableList.builder();
+ MultiFileClassFacade kmMultiFileClassFacade, String packageName, DexItemFactory factory) {
+ ImmutableList.Builder<KotlinTypeReference> builder = ImmutableList.builder();
for (String partClassName : kmMultiFileClassFacade.getPartClassNames()) {
- String descriptor = DescriptorUtils.getDescriptorFromClassBinaryName(partClassName);
- DexType type = definitionSupplier.dexItemFactory().createType(descriptor);
- builder.add(type);
+ builder.add(KotlinTypeReference.fromBinaryName(partClassName, factory));
}
return new KotlinMultiFileClassFacadeInfo(builder.build(), packageName);
}
@@ -59,11 +56,10 @@
KotlinClassMetadata.MultiFileClassFacade.Writer writer =
new KotlinClassMetadata.MultiFileClassFacade.Writer();
List<String> partClassNameStrings = new ArrayList<>(partClassNames.size());
- for (DexType partClassName : partClassNames) {
- if (appView.appInfo().isNonProgramTypeOrLiveProgramType(partClassName)) {
- DexString descriptor = namingLens.lookupDescriptor(partClassName);
- String classifier = DescriptorUtils.descriptorToKotlinClassifier(descriptor.toString());
- partClassNameStrings.add(classifier);
+ for (KotlinTypeReference partClassName : partClassNames) {
+ String binaryName = partClassName.toRenamedBinaryNameOrDefault(appView, namingLens, null);
+ if (binaryName != null) {
+ partClassNameStrings.add(binaryName);
}
}
return writer.write(partClassNameStrings).getHeader();
@@ -73,4 +69,9 @@
public String getPackageName() {
return packageName;
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ forEachApply(partClassNames, type -> type::trace, definitionSupplier);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
index d4ec800..9470082 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMultiFileClassPartInfo.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
@@ -20,6 +21,7 @@
// Holds information about Metadata.MultiFileClassPartInfo
public class KotlinMultiFileClassPartInfo implements KotlinClassLevelInfo {
+ // TODO(b/157630779): Maybe model facadeClassName.
private final String facadeClassName;
private final KotlinPackageInfo packageInfo;
private final String packageName;
@@ -35,13 +37,12 @@
MultiFileClassPart classPart,
String packageName,
DexClass clazz,
- DexDefinitionSupplier definitionSupplier,
+ DexItemFactory factory,
Reporter reporter,
Consumer<DexEncodedMethod> keepByteCode) {
return new KotlinMultiFileClassPartInfo(
classPart.getFacadeClassName(),
- KotlinPackageInfo.create(
- classPart.toKmPackage(), clazz, definitionSupplier, reporter, keepByteCode),
+ KotlinPackageInfo.create(classPart.toKmPackage(), clazz, factory, reporter, keepByteCode),
packageName);
}
@@ -70,4 +71,9 @@
public String getPackageName() {
return packageName;
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ packageInfo.trace(definitionSupplier);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index d98e109..8f0268a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -12,8 +12,10 @@
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Reporter;
import java.util.HashMap;
import java.util.Map;
@@ -22,7 +24,7 @@
import kotlinx.metadata.jvm.JvmExtensionsKt;
// Holds information about a KmPackage object.
-public class KotlinPackageInfo {
+public class KotlinPackageInfo implements EnqueuerMetadataTraceable {
private final String moduleName;
private final KotlinDeclarationContainerInfo containerInfo;
@@ -35,7 +37,7 @@
public static KotlinPackageInfo create(
KmPackage kmPackage,
DexClass clazz,
- DexDefinitionSupplier definitionSupplier,
+ DexItemFactory factory,
Reporter reporter,
Consumer<DexEncodedMethod> keepByteCode) {
Map<String, DexEncodedField> fieldMap = new HashMap<>();
@@ -49,7 +51,7 @@
return new KotlinPackageInfo(
JvmExtensionsKt.getModuleName(kmPackage),
KotlinDeclarationContainerInfo.create(
- kmPackage, methodMap, fieldMap, definitionSupplier, reporter, keepByteCode));
+ kmPackage, methodMap, fieldMap, factory, reporter, keepByteCode));
}
public void rewrite(
@@ -66,4 +68,9 @@
namingLens);
JvmExtensionsKt.setModuleName(kmPackage, moduleName);
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ containerInfo.trace(definitionSupplier);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
index 802428a..4d14c67 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -4,10 +4,13 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
@@ -41,6 +44,8 @@
private final List<KotlinTypeParameterInfo> typeParameters;
+ private final KotlinVersionRequirementInfo versionRequirements;
+
private final int jvmFlags;
private final KotlinJvmFieldSignatureInfo fieldSignature;
@@ -60,6 +65,7 @@
KotlinTypeInfo receiverParameterType,
KotlinValueParameterInfo setterParameter,
List<KotlinTypeParameterInfo> typeParameters,
+ KotlinVersionRequirementInfo versionRequirements,
int jvmFlags,
KotlinJvmFieldSignatureInfo fieldSignature,
KotlinJvmMethodSignatureInfo getterSignature,
@@ -73,6 +79,7 @@
this.receiverParameterType = receiverParameterType;
this.setterParameter = setterParameter;
this.typeParameters = typeParameters;
+ this.versionRequirements = versionRequirements;
this.jvmFlags = jvmFlags;
this.fieldSignature = fieldSignature;
this.getterSignature = getterSignature;
@@ -81,27 +88,25 @@
}
public static KotlinPropertyInfo create(
- KmProperty kmProperty, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+ KmProperty kmProperty, DexItemFactory factory, Reporter reporter) {
return new KotlinPropertyInfo(
kmProperty.getFlags(),
kmProperty.getGetterFlags(),
kmProperty.getSetterFlags(),
kmProperty.getName(),
- KotlinTypeInfo.create(kmProperty.getReturnType(), definitionSupplier, reporter),
- KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), definitionSupplier, reporter),
- KotlinValueParameterInfo.create(
- kmProperty.getSetterParameter(), definitionSupplier, reporter),
- KotlinTypeParameterInfo.create(
- kmProperty.getTypeParameters(), definitionSupplier, reporter),
+ KotlinTypeInfo.create(kmProperty.getReturnType(), factory, reporter),
+ KotlinTypeInfo.create(kmProperty.getReceiverParameterType(), factory, reporter),
+ KotlinValueParameterInfo.create(kmProperty.getSetterParameter(), factory, reporter),
+ KotlinTypeParameterInfo.create(kmProperty.getTypeParameters(), factory, reporter),
+ KotlinVersionRequirementInfo.create(kmProperty.getVersionRequirements()),
JvmExtensionsKt.getJvmFlags(kmProperty),
- KotlinJvmFieldSignatureInfo.create(
- JvmExtensionsKt.getFieldSignature(kmProperty), definitionSupplier),
+ KotlinJvmFieldSignatureInfo.create(JvmExtensionsKt.getFieldSignature(kmProperty), factory),
KotlinJvmMethodSignatureInfo.create(
- JvmExtensionsKt.getGetterSignature(kmProperty), definitionSupplier),
+ JvmExtensionsKt.getGetterSignature(kmProperty), factory),
KotlinJvmMethodSignatureInfo.create(
- JvmExtensionsKt.getSetterSignature(kmProperty), definitionSupplier),
+ JvmExtensionsKt.getSetterSignature(kmProperty), factory),
KotlinJvmMethodSignatureInfo.create(
- JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), definitionSupplier));
+ JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty), factory));
}
@Override
@@ -146,6 +151,7 @@
for (KotlinTypeParameterInfo typeParameter : typeParameters) {
typeParameter.rewrite(kmProperty::visitTypeParameter, appView, namingLens);
}
+ versionRequirements.rewrite(kmProperty::visitVersionRequirement);
JvmPropertyExtensionVisitor extensionVisitor =
(JvmPropertyExtensionVisitor) kmProperty.visitExtensions(JvmPropertyExtensionVisitor.TYPE);
if (extensionVisitor != null) {
@@ -160,4 +166,30 @@
}
}
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ if (returnType != null) {
+ returnType.trace(definitionSupplier);
+ }
+ if (receiverParameterType != null) {
+ receiverParameterType.trace(definitionSupplier);
+ }
+ if (setterParameter != null) {
+ setterParameter.trace(definitionSupplier);
+ }
+ forEachApply(typeParameters, param -> param::trace, definitionSupplier);
+ if (fieldSignature != null) {
+ fieldSignature.trace(definitionSupplier);
+ }
+ if (getterSignature != null) {
+ getterSignature.trace(definitionSupplier);
+ }
+ if (setterSignature != null) {
+ setterSignature.trace(definitionSupplier);
+ }
+ if (syntheticMethodForAnnotations != null) {
+ syntheticMethodForAnnotations.trace(definitionSupplier);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
index 38933b5..5af2a4c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClassInfo.java
@@ -7,6 +7,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.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
@@ -41,7 +42,7 @@
String packageName,
DexClass clazz,
Kotlin kotlin,
- DexDefinitionSupplier definitionSupplier,
+ DexItemFactory factory,
Reporter reporter) {
KmLambda lambda = null;
if (syntheticClass.isLambda()) {
@@ -49,9 +50,7 @@
assert lambda != null;
}
return new KotlinSyntheticClassInfo(
- lambda != null
- ? KotlinLambdaInfo.create(clazz, lambda, definitionSupplier, reporter)
- : null,
+ lambda != null ? KotlinLambdaInfo.create(clazz, lambda, factory, reporter) : null,
getFlavour(syntheticClass, clazz, kotlin),
packageName);
}
@@ -92,6 +91,13 @@
}
@Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ if (lambda != null) {
+ lambda.trace(definitionSupplier);
+ }
+ }
+
+ @Override
public String getPackageName() {
return packageName;
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
index b4b5354..bb75b7f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeAliasInfo.java
@@ -4,17 +4,21 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Reporter;
import java.util.List;
import kotlinx.metadata.KmTypeAlias;
import kotlinx.metadata.KmTypeAliasVisitor;
// Holds information about KmTypeAlias
-public class KotlinTypeAliasInfo {
+public class KotlinTypeAliasInfo implements EnqueuerMetadataTraceable {
private final int flags;
private final String name;
@@ -22,6 +26,7 @@
private final KotlinTypeInfo expandedType;
private final List<KotlinTypeParameterInfo> typeParameters;
private final List<KotlinAnnotationInfo> annotations;
+ private final KotlinVersionRequirementInfo versionRequirements;
private KotlinTypeAliasInfo(
int flags,
@@ -29,7 +34,8 @@
KotlinTypeInfo underlyingType,
KotlinTypeInfo expandedType,
List<KotlinTypeParameterInfo> typeParameters,
- List<KotlinAnnotationInfo> annotations) {
+ List<KotlinAnnotationInfo> annotations,
+ KotlinVersionRequirementInfo versionRequirements) {
this.flags = flags;
this.name = name;
assert underlyingType != null;
@@ -38,17 +44,19 @@
this.expandedType = expandedType;
this.typeParameters = typeParameters;
this.annotations = annotations;
+ this.versionRequirements = versionRequirements;
}
public static KotlinTypeAliasInfo create(
- KmTypeAlias alias, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+ KmTypeAlias alias, DexItemFactory factory, Reporter reporter) {
return new KotlinTypeAliasInfo(
alias.getFlags(),
alias.getName(),
- KotlinTypeInfo.create(alias.underlyingType, definitionSupplier, reporter),
- KotlinTypeInfo.create(alias.expandedType, definitionSupplier, reporter),
- KotlinTypeParameterInfo.create(alias.getTypeParameters(), definitionSupplier, reporter),
- KotlinAnnotationInfo.create(alias.getAnnotations(), definitionSupplier));
+ KotlinTypeInfo.create(alias.underlyingType, factory, reporter),
+ KotlinTypeInfo.create(alias.expandedType, factory, reporter),
+ KotlinTypeParameterInfo.create(alias.getTypeParameters(), factory, reporter),
+ KotlinAnnotationInfo.create(alias.getAnnotations(), factory),
+ KotlinVersionRequirementInfo.create(alias.getVersionRequirements()));
}
void rewrite(
@@ -64,5 +72,14 @@
for (KotlinAnnotationInfo annotation : annotations) {
annotation.rewrite(kmTypeAliasVisitor::visitAnnotation, appView, namingLens);
}
+ versionRequirements.rewrite(kmTypeAliasVisitor::visitVersionRequirement);
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ underlyingType.trace(definitionSupplier);
+ expandedType.trace(definitionSupplier);
+ forEachApply(typeParameters, typeParam -> typeParam::trace, definitionSupplier);
+ forEachApply(annotations, annotation -> annotation::trace, definitionSupplier);
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
index 8601667..f21db98 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -4,10 +4,14 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -18,7 +22,7 @@
import kotlinx.metadata.jvm.JvmTypeExtensionVisitor;
// Provides access to Kotlin information about a kotlin type.
-public class KotlinTypeInfo {
+public class KotlinTypeInfo implements EnqueuerMetadataTraceable {
private static final List<KotlinTypeProjectionInfo> EMPTY_ARGUMENTS = ImmutableList.of();
@@ -47,32 +51,29 @@
this.flexibleTypeUpperBoundInfo = flexibleTypeUpperBoundInfo;
}
- static KotlinTypeInfo create(
- KmType kmType, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+ static KotlinTypeInfo create(KmType kmType, DexItemFactory factory, Reporter reporter) {
if (kmType == null) {
return null;
}
return new KotlinTypeInfo(
kmType.getFlags(),
- KotlinClassifierInfo.create(kmType.classifier, definitionSupplier, reporter),
- KotlinTypeInfo.create(kmType.getAbbreviatedType(), definitionSupplier, reporter),
- KotlinTypeInfo.create(kmType.getOuterType(), definitionSupplier, reporter),
- getArguments(kmType.getArguments(), definitionSupplier, reporter),
- KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), definitionSupplier),
+ KotlinClassifierInfo.create(kmType.classifier, factory, reporter),
+ KotlinTypeInfo.create(kmType.getAbbreviatedType(), factory, reporter),
+ KotlinTypeInfo.create(kmType.getOuterType(), factory, reporter),
+ getArguments(kmType.getArguments(), factory, reporter),
+ KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmType), factory),
KotlinFlexibleTypeUpperBoundInfo.create(
- kmType.getFlexibleTypeUpperBound(), definitionSupplier, reporter));
+ kmType.getFlexibleTypeUpperBound(), factory, reporter));
}
static List<KotlinTypeProjectionInfo> getArguments(
- List<KmTypeProjection> projections,
- DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ List<KmTypeProjection> projections, DexItemFactory factory, Reporter reporter) {
if (projections.isEmpty()) {
return EMPTY_ARGUMENTS;
}
ImmutableList.Builder<KotlinTypeProjectionInfo> arguments = ImmutableList.builder();
for (KmTypeProjection projection : projections) {
- arguments.add(KotlinTypeProjectionInfo.create(projection, definitionSupplier, reporter));
+ arguments.add(KotlinTypeProjectionInfo.create(projection, factory, reporter));
}
return arguments.build();
}
@@ -107,4 +108,18 @@
}
}
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ classifier.trace(definitionSupplier);
+ if (abbreviatedType != null) {
+ abbreviatedType.trace(definitionSupplier);
+ }
+ if (outerType != null) {
+ outerType.trace(definitionSupplier);
+ }
+ forEachApply(arguments, argument -> argument::trace, definitionSupplier);
+ flexibleTypeUpperBoundInfo.trace(definitionSupplier);
+ forEachApply(annotations, annotation -> annotation::trace, definitionSupplier);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
index fd11c11..53c2eb2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
@@ -4,10 +4,14 @@
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
import java.util.List;
@@ -19,7 +23,7 @@
import kotlinx.metadata.jvm.JvmTypeParameterExtensionVisitor;
// Provides access to Kotlin information about a type-parameter.
-public class KotlinTypeParameterInfo {
+public class KotlinTypeParameterInfo implements EnqueuerMetadataTraceable {
private static final List<KotlinTypeParameterInfo> EMPTY_TYPE_PARAMETERS = ImmutableList.of();
private static final List<KotlinTypeInfo> EMPTY_UPPER_BOUNDS = ImmutableList.of();
@@ -47,41 +51,36 @@
}
private static KotlinTypeParameterInfo create(
- KmTypeParameter kmTypeParameter,
- DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ KmTypeParameter kmTypeParameter, DexItemFactory factory, Reporter reporter) {
return new KotlinTypeParameterInfo(
kmTypeParameter.getFlags(),
kmTypeParameter.getId(),
kmTypeParameter.getName(),
kmTypeParameter.getVariance(),
- getUpperBounds(kmTypeParameter.getUpperBounds(), definitionSupplier, reporter),
- KotlinAnnotationInfo.create(
- JvmExtensionsKt.getAnnotations(kmTypeParameter), definitionSupplier));
+ getUpperBounds(kmTypeParameter.getUpperBounds(), factory, reporter),
+ KotlinAnnotationInfo.create(JvmExtensionsKt.getAnnotations(kmTypeParameter), factory));
}
static List<KotlinTypeParameterInfo> create(
- List<KmTypeParameter> kmTypeParameters,
- DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ List<KmTypeParameter> kmTypeParameters, DexItemFactory factory, Reporter reporter) {
if (kmTypeParameters.isEmpty()) {
return EMPTY_TYPE_PARAMETERS;
}
ImmutableList.Builder<KotlinTypeParameterInfo> builder = ImmutableList.builder();
for (KmTypeParameter kmTypeParameter : kmTypeParameters) {
- builder.add(create(kmTypeParameter, definitionSupplier, reporter));
+ builder.add(create(kmTypeParameter, factory, reporter));
}
return builder.build();
}
private static List<KotlinTypeInfo> getUpperBounds(
- List<KmType> upperBounds, DexDefinitionSupplier definitionSupplier, Reporter reporter) {
+ List<KmType> upperBounds, DexItemFactory factory, Reporter reporter) {
if (upperBounds.isEmpty()) {
return EMPTY_UPPER_BOUNDS;
}
ImmutableList.Builder<KotlinTypeInfo> builder = ImmutableList.builder();
for (KmType upperBound : upperBounds) {
- builder.add(KotlinTypeInfo.create(upperBound, definitionSupplier, reporter));
+ builder.add(KotlinTypeInfo.create(upperBound, factory, reporter));
}
return builder.build();
}
@@ -104,4 +103,10 @@
annotation.rewrite(extensionVisitor::visitAnnotation, appView, namingLens);
}
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ forEachApply(originalUpperBounds, upperBound -> upperBound::trace, definitionSupplier);
+ forEachApply(annotations, annotation -> annotation::trace, definitionSupplier);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
index 68da5db..9ab3de0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -6,14 +6,16 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Reporter;
import kotlinx.metadata.KmTypeProjection;
import kotlinx.metadata.KmVariance;
// Provides access to Kotlin information about the type projection of a type (arguments).
-public class KotlinTypeProjectionInfo {
+public class KotlinTypeProjectionInfo implements EnqueuerMetadataTraceable {
final KmVariance variance;
final KotlinTypeInfo typeInfo;
@@ -24,12 +26,10 @@
}
static KotlinTypeProjectionInfo create(
- KmTypeProjection kmTypeProjection,
- DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ KmTypeProjection kmTypeProjection, DexItemFactory factory, Reporter reporter) {
return new KotlinTypeProjectionInfo(
kmTypeProjection.getVariance(),
- KotlinTypeInfo.create(kmTypeProjection.getType(), definitionSupplier, reporter));
+ KotlinTypeInfo.create(kmTypeProjection.getType(), factory, reporter));
}
private boolean isStarProjection() {
@@ -47,4 +47,11 @@
typeInfo.rewrite(flags -> visitorProvider.get(flags, variance), appView, namingLens);
}
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ if (typeInfo != null) {
+ typeInfo.trace(definitionSupplier);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
new file mode 100644
index 0000000..dd2be72
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeReference.java
@@ -0,0 +1,108 @@
+// 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.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.DescriptorUtils;
+
+/**
+ * To account for invalid type references in kotlin metadata, the class KotlinTypeReference will
+ * either hold a DexType reference, or a String, with the original name reference, which is not a
+ * valid jvm descriptor/name. The values will be disjoint.
+ */
+class KotlinTypeReference implements EnqueuerMetadataTraceable {
+
+ private final DexType known;
+ private final String unknown;
+
+ private KotlinTypeReference(DexType known) {
+ this.known = known;
+ this.unknown = null;
+ assert known != null;
+ }
+
+ private KotlinTypeReference(String unknown) {
+ this.known = null;
+ this.unknown = unknown;
+ assert unknown != null;
+ }
+
+ static KotlinTypeReference fromBinaryName(String binaryName, DexItemFactory factory) {
+ if (DescriptorUtils.isValidBinaryName(binaryName)) {
+ return fromDescriptor(
+ DescriptorUtils.getDescriptorFromClassBinaryName(binaryName), factory, binaryName);
+ }
+ return new KotlinTypeReference(binaryName);
+ }
+
+ static KotlinTypeReference fromDescriptor(String descriptor, DexItemFactory factory) {
+ return fromDescriptor(descriptor, factory, descriptor);
+ }
+
+ static KotlinTypeReference fromDescriptor(
+ String descriptor, DexItemFactory factory, String unknownValue) {
+ if (DescriptorUtils.isDescriptor(descriptor)) {
+ DexType type = factory.createType(descriptor);
+ return new KotlinTypeReference(type);
+ }
+ return new KotlinTypeReference(unknownValue);
+ }
+
+ String toRenamedDescriptorOrDefault(
+ AppView<AppInfoWithLiveness> appView, NamingLens namingLens, String defaultValue) {
+ if (unknown != null) {
+ return unknown;
+ }
+ assert known != null;
+ if (!known.isClassType()) {
+ return known.descriptor.toString();
+ }
+ if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(known)) {
+ return defaultValue;
+ }
+ DexString descriptor = namingLens.lookupDescriptor(known);
+ if (descriptor != null) {
+ return descriptor.toString();
+ }
+ return defaultValue;
+ }
+
+ String toRenamedBinaryNameOrDefault(
+ AppView<AppInfoWithLiveness> appView, NamingLens namingLens, String defaultValue) {
+ if (unknown != null) {
+ // Unknown values are always on the input form, so we can just return it.
+ return unknown;
+ }
+ String descriptor = toRenamedDescriptorOrDefault(appView, namingLens, defaultValue);
+ if (descriptor == null) {
+ return null;
+ }
+ if (descriptor.equals(defaultValue)) {
+ // We assume that the default value passed in is already a binary name.
+ return descriptor;
+ }
+ return DescriptorUtils.getBinaryNameFromDescriptor(descriptor);
+ }
+
+ @Override
+ public String toString() {
+ return known != null ? known.descriptor.toString() : unknown;
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ if (known != null && known.isClassType()) {
+ // Lookup the definition, ignoring the result. This populates the sets in the Enqueuer.
+ definitionSupplier.definitionFor(known);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
index ee2f99b..83dd5d2 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinValueParameterInfo.java
@@ -6,17 +6,20 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
import java.util.List;
import kotlinx.metadata.KmType;
import kotlinx.metadata.KmValueParameter;
import kotlinx.metadata.KmValueParameterVisitor;
+import kotlinx.metadata.internal.metadata.deserialization.Flags;
// Provides access to Kotlin information about value parameter.
-class KotlinValueParameterInfo {
+class KotlinValueParameterInfo implements EnqueuerMetadataTraceable {
private static final List<KotlinValueParameterInfo> EMPTY_VALUE_PARAMETERS = ImmutableList.of();
// Original parameter name.
final String name;
@@ -35,10 +38,12 @@
this.varargElementType = varargElementType;
}
+ boolean isCrossInline() {
+ return Flags.IS_CROSSINLINE.get(flags);
+ }
+
static KotlinValueParameterInfo create(
- KmValueParameter kmValueParameter,
- DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ KmValueParameter kmValueParameter, DexItemFactory factory, Reporter reporter) {
if (kmValueParameter == null) {
return null;
}
@@ -46,21 +51,18 @@
return new KotlinValueParameterInfo(
kmValueParameter.getFlags(),
kmValueParameter.getName(),
- KotlinTypeInfo.create(kmType, definitionSupplier, reporter),
- KotlinTypeInfo.create(
- kmValueParameter.getVarargElementType(), definitionSupplier, reporter));
+ KotlinTypeInfo.create(kmType, factory, reporter),
+ KotlinTypeInfo.create(kmValueParameter.getVarargElementType(), factory, reporter));
}
static List<KotlinValueParameterInfo> create(
- List<KmValueParameter> parameters,
- DexDefinitionSupplier definitionSupplier,
- Reporter reporter) {
+ List<KmValueParameter> parameters, DexItemFactory factory, Reporter reporter) {
if (parameters.isEmpty()) {
return EMPTY_VALUE_PARAMETERS;
}
ImmutableList.Builder<KotlinValueParameterInfo> builder = ImmutableList.builder();
for (KmValueParameter parameter : parameters) {
- builder.add(create(parameter, definitionSupplier, reporter));
+ builder.add(create(parameter, factory, reporter));
}
return builder.build();
}
@@ -76,4 +78,12 @@
kmValueParameterVisitor::visitVarargElementType, appView, namingLens);
}
}
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ type.trace(definitionSupplier);
+ if (varargElementType != null) {
+ varargElementType.trace(definitionSupplier);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
new file mode 100644
index 0000000..9530f4c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinVersionRequirementInfo.java
@@ -0,0 +1,82 @@
+// 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.kotlin;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmVersion;
+import kotlinx.metadata.KmVersionRequirement;
+import kotlinx.metadata.KmVersionRequirementLevel;
+import kotlinx.metadata.KmVersionRequirementVersionKind;
+import kotlinx.metadata.KmVersionRequirementVisitor;
+
+class KotlinVersionRequirementInfo {
+
+ private static final KotlinVersionRequirementInfo NO_VERSION_REQUIREMENTS =
+ new KotlinVersionRequirementInfo(ImmutableList.of());
+
+ private final List<KotlinVersionRequirementPoint> versionRequirements;
+
+ private KotlinVersionRequirementInfo(List<KotlinVersionRequirementPoint> versionRequirements) {
+ this.versionRequirements = versionRequirements;
+ }
+
+ static KotlinVersionRequirementInfo create(List<KmVersionRequirement> kmVersionRequirements) {
+ if (kmVersionRequirements.isEmpty()) {
+ return NO_VERSION_REQUIREMENTS;
+ }
+ ImmutableList.Builder<KotlinVersionRequirementPoint> builder = ImmutableList.builder();
+ for (KmVersionRequirement kmVersionRequirement : kmVersionRequirements) {
+ builder.add(KotlinVersionRequirementPoint.create(kmVersionRequirement));
+ }
+ return new KotlinVersionRequirementInfo(builder.build());
+ }
+
+ public void rewrite(KmVisitorProviders.KmVersionRequirementVisitorProvider visitorProvider) {
+ if (this == NO_VERSION_REQUIREMENTS) {
+ return;
+ }
+ for (KotlinVersionRequirementPoint versionRequirement : versionRequirements) {
+ versionRequirement.rewrite(visitorProvider.get());
+ }
+ }
+
+ private static class KotlinVersionRequirementPoint {
+
+ private final Integer errorCode;
+ private final KmVersionRequirementVersionKind kind;
+ private final KmVersionRequirementLevel level;
+ private final String message;
+ private final KmVersion version;
+
+ private KotlinVersionRequirementPoint(
+ KmVersionRequirementVersionKind kind,
+ KmVersionRequirementLevel level,
+ Integer errorCode,
+ String message,
+ KmVersion version) {
+ this.errorCode = errorCode;
+ this.kind = kind;
+ this.level = level;
+ this.message = message;
+ this.version = version;
+ }
+
+ private static KotlinVersionRequirementPoint create(KmVersionRequirement kmVersionRequirement) {
+ return new KotlinVersionRequirementPoint(
+ kmVersionRequirement.kind,
+ kmVersionRequirement.level,
+ kmVersionRequirement.getErrorCode(),
+ kmVersionRequirement.getMessage(),
+ kmVersionRequirement.version);
+ }
+
+ private void rewrite(KmVersionRequirementVisitor visitor) {
+ visitor.visit(kind, level, errorCode, message);
+ visitor.visitVersion(version.getMajor(), version.getMinor(), version.getPatch());
+ visitor.visitEnd();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 8cffa05..b014c4f 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -3,15 +3,19 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.BottomUpClassHierarchyTraversal;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
+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.FieldAccessInfo;
import com.android.tools.r8.graph.FieldAccessInfoCollection;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -178,9 +182,7 @@
getOrCreateReservedFieldNamingState(clazz.type);
FieldNamingState state = parentState.createChildState(reservedNames);
if (clazz.isProgramClass()) {
- for (DexEncodedField field : clazz.fields()) {
- renameField(field, state);
- }
+ clazz.asProgramClass().forEachProgramField(field -> renameField(field, state));
}
assert !states.containsKey(clazz.type);
@@ -212,11 +214,14 @@
for (DexClass clazz : partition) {
if (clazz.isProgramClass()) {
assert clazz.isInterface();
- for (DexEncodedField field : clazz.fields()) {
- DexString newName = renameField(field, state);
- namesToBeReservedInImplementsSubclasses.markReservedDirectly(
- newName, field.field.name, field.field.type);
- }
+ clazz
+ .asProgramClass()
+ .forEachProgramField(
+ field -> {
+ DexString newName = renameField(field, state);
+ namesToBeReservedInImplementsSubclasses.markReservedDirectly(
+ newName, field.getReference().name, field.getReference().type);
+ });
}
}
@@ -236,11 +241,10 @@
}
}
- private DexString renameField(DexEncodedField encodedField, FieldNamingState state) {
- DexField field = encodedField.field;
+ private DexString renameField(ProgramField field, FieldNamingState state) {
DexString newName = state.getOrCreateNameFor(field);
- if (newName != field.name) {
- renaming.put(field, newName);
+ if (newName != field.getReference().name) {
+ renaming.put(field.getReference(), newName);
}
return newName;
}
@@ -256,32 +260,17 @@
}
private void renameNonReboundAccessToField(DexField field) {
- // Already renamed
+ // If the given field reference is a non-rebound reference to a program field, then assign the
+ // same name as the resolved field.
if (renaming.containsKey(field)) {
return;
}
- DexEncodedField definition = appView.definitionFor(field);
- if (definition != null) {
- assert definition.field == field;
+ DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(field));
+ if (holder == null) {
return;
}
- // Now, `field` is reference. Find its definition and check if it's renamed.
- DexClass holder = appView.definitionFor(field.holder);
- // We don't care pruned types or library classes.
- if (holder == null || holder.isNotProgramClass()) {
- return;
- }
- definition = appView.appInfo().resolveField(field).getResolvedField();
- if (definition == null) {
- // The program is already broken in the sense that it has an unresolvable field reference.
- // Leave it as-is.
- return;
- }
- assert definition.field != field;
- assert definition.holder() != field.holder;
- // If the definition is renamed,
- if (renaming.containsKey(definition.field)) {
- // Assign the same, renamed name as the definition to the reference.
+ DexEncodedField definition = appView.appInfo().resolveFieldOn(holder, field).getResolvedField();
+ if (definition != null && definition.field != field && renaming.containsKey(definition.field)) {
renaming.put(field, renaming.get(definition.field));
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
index f8d80cb..35e3d3f 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -6,11 +6,9 @@
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.DexEncodedField;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.naming.FieldNamingState.InternalState;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -20,7 +18,7 @@
private final ReservedFieldNamingState reservedNames;
private final MemberNamingStrategy strategy;
- private final BiPredicate<DexString, DexField> isAvailable;
+ private final BiPredicate<DexString, ProgramField> isAvailable;
public FieldNamingState(
AppView<? extends AppInfoWithClassHierarchy> appView, MemberNamingStrategy strategy) {
@@ -42,7 +40,8 @@
super(appView, internalStates);
this.reservedNames = reservedNames;
this.strategy = strategy;
- this.isAvailable = (newName, field) -> !reservedNames.isReserved(newName, field.type);
+ this.isAvailable =
+ (newName, field) -> !reservedNames.isReserved(newName, field.getReference().type);
}
public FieldNamingState createChildState(ReservedFieldNamingState reservedNames) {
@@ -52,20 +51,13 @@
return childState;
}
- public DexString getOrCreateNameFor(DexField field) {
- DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
- if (encodedField != null) {
- DexClass clazz = appView.definitionFor(encodedField.holder());
- if (clazz == null) {
- return field.name;
- }
- DexString reservedName = strategy.getReservedName(encodedField, clazz);
- if (reservedName != null) {
- return reservedName;
- }
+ public DexString getOrCreateNameFor(ProgramField field) {
+ DexString reservedName = strategy.getReservedName(field.getDefinition(), field.getHolder());
+ if (reservedName != null) {
+ return reservedName;
}
// TODO(b/133208730) If we cannot resolve the field, are we then allowed to rename it?
- return getOrCreateInternalState(field).createNewName(field);
+ return getOrCreateInternalState(field.getReference()).createNewName(field);
}
public void includeReservations(ReservedFieldNamingState reservedNames) {
@@ -100,9 +92,9 @@
this.nextNameIndex = nextNameIndex;
}
- public DexString createNewName(DexField field) {
+ public DexString createNewName(ProgramField field) {
DexString name = strategy.next(field, this, isAvailable);
- assert !reservedNames.isReserved(name, field.type);
+ assert !reservedNames.isReserved(name, field.getReference().type);
return name;
}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index bacc7e0..cddbea8 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -7,9 +7,9 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.ProgramField;
import java.util.function.BiPredicate;
public interface MemberNamingStrategy {
@@ -20,9 +20,9 @@
BiPredicate<DexString, DexMethod> isAvailable);
DexString next(
- DexField field,
+ ProgramField field,
InternalNamingState internalState,
- BiPredicate<DexString, DexField> isAvailable);
+ BiPredicate<DexString, ProgramField> isAvailable);
DexString getReservedName(DexEncodedMethod method, DexClass holder);
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 8f1a41a..d0f0381 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -11,11 +11,11 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
@@ -240,10 +240,10 @@
@Override
public DexString next(
- DexField field,
+ ProgramField field,
InternalNamingState internalState,
- BiPredicate<DexString, DexField> isAvailable) {
- assert checkAllowMemberRenaming(field.holder);
+ BiPredicate<DexString, ProgramField> isAvailable) {
+ assert checkAllowMemberRenaming(field.getHolderType());
DexString candidate;
do {
candidate = getNextName(internalState, false);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 3ce8e7f..e720b30 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.kotlin.KotlinMetadataRewriter;
@@ -32,7 +33,6 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.TriFunction;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
@@ -301,8 +301,9 @@
DexField originalField = ((FieldSignature) signature).toDexField(factory, type);
addMemberNaming(
originalField, memberNaming, addToAdditionalMaps ? additionalFieldNamings : null);
- DexEncodedField encodedField = appView.definitionFor(originalField);
- if (encodedField == null || !encodedField.accessFlags.isPrivate()) {
+ DexClass holder = appView.definitionForHolder(originalField);
+ DexEncodedField field = originalField.lookupOnClass(holder);
+ if (field == null || !field.isPrivate()) {
nonPrivateMembers.put(originalField, memberNaming);
}
}
@@ -465,62 +466,46 @@
@Override
public DexString next(
- DexMethod method,
+ DexMethod reference,
InternalNamingState internalState,
BiPredicate<DexString, DexMethod> isAvailable) {
- DexEncodedMethod definition = appView.definitionFor(method);
- DexString nextName =
- nextName(
- method,
- definition,
- method.name,
- method.holder,
- internalState,
- isAvailable,
- super::next);
- assert nextName == method.name || !definition.isClassInitializer();
- assert nextName == method.name
- || !appView.definitionFor(method.holder).accessFlags.isAnnotation();
+ DexClass holder = appView.definitionForHolder(reference);
+ assert holder != null;
+ DexEncodedMethod method = holder.lookupMethod(reference);
+ DexString reservedName = getReservedName(method, reference.name, holder);
+ DexString nextName;
+ if (reservedName != null) {
+ if (!isAvailable.test(reservedName, reference)) {
+ reportReservationError(reference, reservedName);
+ }
+ nextName = reservedName;
+ } else {
+ assert !mappedNames.containsKey(reference);
+ assert appView.rootSet().mayBeMinified(reference, appView);
+ nextName = super.next(reference, internalState, isAvailable);
+ }
+ assert nextName == reference.name || !method.isInitializer();
+ assert nextName == reference.name || !holder.isAnnotation();
return nextName;
}
@Override
public DexString next(
- DexField field,
+ ProgramField field,
InternalNamingState internalState,
- BiPredicate<DexString, DexField> isAvailable) {
- return nextName(
- field,
- appView.definitionFor(field),
- field.name,
- field.holder,
- internalState,
- isAvailable,
- super::next);
- }
-
- private <T extends DexReference> DexString nextName(
- T reference,
- DexDefinition definition,
- DexString name,
- DexType holderType,
- InternalNamingState internalState,
- BiPredicate<DexString, T> isAvailable,
- TriFunction<T, InternalNamingState, BiPredicate<DexString, T>, DexString> generateName) {
- assert definition.isDexEncodedMethod() || definition.isDexEncodedField();
- assert definition.toReference() == reference;
- DexClass holder = appView.definitionFor(holderType);
- assert holder != null;
- DexString reservedName = getReservedName(definition, name, holder);
+ BiPredicate<DexString, ProgramField> isAvailable) {
+ DexField reference = field.getReference();
+ DexString reservedName =
+ getReservedName(field.getDefinition(), reference.name, field.getHolder());
if (reservedName != null) {
- if (!isAvailable.test(reservedName, reference)) {
- reportReservationError(definition.toReference(), reservedName);
+ if (!isAvailable.test(reservedName, field)) {
+ reportReservationError(reference, reservedName);
}
return reservedName;
}
assert !mappedNames.containsKey(reference);
assert appView.rootSet().mayBeMinified(reference, appView);
- return generateName.apply(reference, internalState, isAvailable);
+ return super.next(field, internalState, isAvailable);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
index 31e239b..54decce 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoisting.java
@@ -254,8 +254,9 @@
// Remove all of the bridges in the eligible subclasses.
for (DexProgramClass subclass : eligibleSubclasses) {
+ assert !appView.appInfo().isPinned(method);
DexEncodedMethod removed = subclass.removeMethod(method);
- assert removed != null && !appView.appInfo().isPinned(removed.method);
+ assert removed != null;
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index b992f8d..1c1de57 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -347,9 +347,9 @@
computeMethodRebinding(appInfo.directInvokes, this::anyLookup, Type.DIRECT);
// Likewise static invokes.
computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC);
-
computeFieldRebinding();
-
- return builder.build(lense);
+ GraphLense lens = builder.build(lense);
+ appInfo.getFieldAccessInfoCollection().flattenAccessContexts();
+ return lens;
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index 95e2772..01c923d 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -15,7 +15,6 @@
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
-import java.util.Set;
public class MemberRebindingLense extends NestedGraphLense {
@@ -99,11 +98,6 @@
}
@Override
- public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
- return previousLense.lookupMethodInAllContexts(method);
- }
-
- @Override
protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
return super.mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 584b84b..3c0d9dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -258,9 +258,9 @@
}
private boolean enclosingMethodPinned(DexClass clazz) {
- return clazz.getEnclosingMethod() != null
- && clazz.getEnclosingMethod().getEnclosingClass() != null
- && appView.appInfo().isPinned(clazz.getEnclosingMethod().getEnclosingClass());
+ return clazz.getEnclosingMethodAttribute() != null
+ && clazz.getEnclosingMethodAttribute().getEnclosingClass() != null
+ && appView.appInfo().isPinned(clazz.getEnclosingMethodAttribute().getEnclosingClass());
}
private static boolean hasInnerClassesFromSet(DexProgramClass clazz, Set<DexType> innerClasses) {
@@ -292,7 +292,7 @@
}
if (keptAnyway || keepForThisInnerClass || keepForThisEnclosingClass) {
if (!keep.enclosingMethod) {
- clazz.clearEnclosingMethod();
+ clazz.clearEnclosingMethodAttribute();
}
if (!keep.innerClasses) {
clazz.clearInnerClasses();
@@ -305,14 +305,15 @@
if (appView.appInfo().isPinned(ica.getInner())) {
return false;
}
- if (appView.appInfo().isPinned(ica.getOuter())) {
+ DexType outer = ica.getOuter();
+ if (outer != null && appView.appInfo().isPinned(outer)) {
return false;
}
if (finalKeepForThisInnerClass && ica.getInner() == clazz.type) {
return false;
}
if (finalKeepForThisEnclosingClass
- && ica.getOuter() == clazz.type
+ && outer == clazz.type
&& classesToRetainInnerClassAttributeFor.contains(ica.getInner())) {
return false;
}
@@ -322,7 +323,7 @@
} else {
// These attributes are only relevant for reflection, and this class is not used for
// reflection. (Note that clearing these attributes can enable more vertical class merging.)
- clazz.clearEnclosingMethod();
+ clazz.clearEnclosingMethodAttribute();
clazz.clearInnerClasses();
}
}
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 275b882..87bfd9a 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -5,7 +5,6 @@
import static com.android.tools.r8.graph.DexEncodedMethod.asProgramMethodOrNull;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
import static com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult.isOverriding;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -27,6 +26,7 @@
import com.android.tools.r8.graph.FieldAccessInfoCollection;
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldAccessInfoImpl;
+import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.graph.InstantiatedSubTypeInfo;
@@ -44,6 +44,7 @@
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
+import com.android.tools.r8.utils.AssertionUtils;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
@@ -51,9 +52,6 @@
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.Visibility;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSortedSet;
-import com.google.common.collect.ImmutableSortedSet.Builder;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
@@ -63,13 +61,11 @@
import java.util.Deque;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.function.Consumer;
-import java.util.function.Function;
import java.util.stream.Collectors;
/** Encapsulates liveness and reachability information for an application. */
@@ -136,10 +132,8 @@
* will have been removed from the code.
*/
public final Set<DexCallSite> callSites;
- /** Set of all items that have to be kept independent of whether they are used. */
- final Set<DexReference> pinnedItems;
- /** Set of kept items that are allowed to be publicized. */
- final Set<DexReference> allowAccessModification;
+ /** Collection of keep requirements for the program. */
+ private final KeepInfoCollection keepInfo;
/** All items with assumemayhavesideeffects rule. */
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
/** All items with assumenosideeffects rule. */
@@ -217,8 +211,7 @@
SortedMap<DexMethod, ProgramMethodSet> directInvokes,
SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
Set<DexCallSite> callSites,
- Set<DexReference> pinnedItems,
- Set<DexReference> allowAccessModification,
+ KeepInfoCollection keepInfo,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
Map<DexReference, ProguardMemberRule> assumedValues,
@@ -253,8 +246,7 @@
this.liveMethods = liveMethods;
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
- this.pinnedItems = pinnedItems;
- this.allowAccessModification = allowAccessModification;
+ this.keepInfo = keepInfo;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
this.assumedValues = assumedValues;
@@ -304,8 +296,7 @@
SortedMap<DexMethod, ProgramMethodSet> directInvokes,
SortedMap<DexMethod, ProgramMethodSet> staticInvokes,
Set<DexCallSite> callSites,
- Set<DexReference> pinnedItems,
- Set<DexReference> allowAccessModification,
+ KeepInfoCollection keepInfo,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
Map<DexReference, ProguardMemberRule> assumedValues,
@@ -340,8 +331,7 @@
this.liveMethods = liveMethods;
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
- this.pinnedItems = pinnedItems;
- this.allowAccessModification = allowAccessModification;
+ this.keepInfo = keepInfo;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
this.assumedValues = assumedValues;
@@ -392,8 +382,7 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- previous.pinnedItems,
- previous.allowAccessModification,
+ previous.keepInfo,
previous.mayHaveSideEffects,
previous.noSideEffects,
previous.assumedValues,
@@ -443,10 +432,7 @@
previous.directInvokes,
previous.staticInvokes,
previous.callSites,
- additionalPinnedItems == null
- ? previous.pinnedItems
- : CollectionUtils.mergeSets(previous.pinnedItems, additionalPinnedItems),
- previous.allowAccessModification,
+ extendPinnedItems(previous, additionalPinnedItems),
previous.mayHaveSideEffects,
previous.noSideEffects,
previous.assumedValues,
@@ -471,7 +457,44 @@
previous.constClassReferences,
previous.initClassReferences);
copyMetadataFromPrevious(previous);
- assert removedClasses == null || assertNoItemRemoved(previous.pinnedItems, removedClasses);
+ assert keepInfo.verifyNoneArePinned(removedClasses, previous);
+ }
+
+ private static KeepInfoCollection extendPinnedItems(
+ AppInfoWithLiveness previous, Collection<DexReference> additionalPinnedItems) {
+ if (additionalPinnedItems == null || additionalPinnedItems.isEmpty()) {
+ return previous.keepInfo;
+ }
+ return previous.keepInfo.mutate(
+ collection -> {
+ for (DexReference reference : additionalPinnedItems) {
+ if (reference.isDexType()) {
+ DexProgramClass clazz =
+ asProgramClassOrNull(previous.definitionFor(reference.asDexType()));
+ if (clazz != null) {
+ collection.pinClass(clazz);
+ }
+ } else if (reference.isDexMethod()) {
+ DexMethod method = reference.asDexMethod();
+ DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(method.holder));
+ if (clazz != null) {
+ DexEncodedMethod definition = clazz.lookupMethod(method);
+ if (definition != null) {
+ collection.pinMethod(clazz, definition);
+ }
+ }
+ } else {
+ DexField field = reference.asDexField();
+ DexProgramClass clazz = asProgramClassOrNull(previous.definitionFor(field.holder));
+ if (clazz != null) {
+ DexEncodedField definition = clazz.lookupField(field);
+ if (definition != null) {
+ collection.pinField(clazz, definition);
+ }
+ }
+ }
+ }
+ });
}
public AppInfoWithLiveness(
@@ -491,8 +514,7 @@
this.liveMethods = previous.liveMethods;
this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
- this.pinnedItems = previous.pinnedItems;
- this.allowAccessModification = previous.allowAccessModification;
+ this.keepInfo = previous.keepInfo;
this.mayHaveSideEffects = previous.mayHaveSideEffects;
this.noSideEffects = previous.noSideEffects;
this.assumedValues = previous.assumedValues;
@@ -543,8 +565,6 @@
|| TwrCloseResourceRewriter.isUtilityClassDescriptor(type)
// TODO(b/150736225): Not sure how to remove these.
|| DesugaredLibraryAPIConverter.isVivifiedType(type)
- // TODO(b/149363884): Handle references to dead proto builders.
- || type.toDescriptorString().endsWith("$Builder;")
: "Failed lookup of non-missing type: " + type;
return definition;
}
@@ -754,23 +774,6 @@
singleTargetLookupCache.removeInstantiatedType(clazz.type, this);
}
- private boolean assertNoItemRemoved(Collection<DexReference> items, Collection<DexType> types) {
- Set<DexType> typeSet = ImmutableSet.copyOf(types);
- for (DexReference item : items) {
- DexType typeToCheck;
- if (item.isDexType()) {
- typeToCheck = item.asDexType();
- } else if (item.isDexMethod()) {
- typeToCheck = item.asDexMethod().holder;
- } else {
- assert item.isDexField();
- typeToCheck = item.asDexField().holder;
- }
- assert !typeSet.contains(typeToCheck);
- }
- return true;
- }
-
private boolean isInstantiatedDirectly(DexProgramClass clazz) {
assert checkIfObsolete();
DexType type = clazz.type;
@@ -797,7 +800,7 @@
if (info != null && info.isRead()) {
return true;
}
- return isPinned(field)
+ return keepInfo.isPinned(field, this)
// Fields in the class that is synthesized by D8/R8 would be used soon.
|| field.holder.isD8R8SynthesizedClassType()
// For library classes we don't know whether a field is read.
@@ -869,7 +872,7 @@
return method.getDefinition().hasCode()
&& !method.getDefinition().isLibraryMethodOverride().isPossiblyTrue()
&& !neverReprocess.contains(reference)
- && !pinnedItems.contains(reference);
+ && !keepInfo.getMethodInfo(method).isPinned();
}
public boolean mayPropagateValueFor(DexReference reference) {
@@ -884,27 +887,15 @@
return holder == null || holder.isLibraryClass() || holder.isClasspathClass();
}
- private static <T extends PresortedComparable<T>> ImmutableSortedSet<T> rewriteItems(
- Set<T> original, Function<T, T> rewrite) {
- Builder<T> builder = new Builder<>(PresortedComparable::slowCompare);
- for (T item : original) {
- builder.add(rewrite.apply(item));
- }
- return builder.build();
- }
-
- private static <T extends PresortedComparable<T>>
- SortedMap<T, ProgramMethodSet> rewriteKeysConservativelyWhileMergingValues(
- Map<T, ProgramMethodSet> original, Function<T, Set<T>> rewrite) {
- SortedMap<T, ProgramMethodSet> result = new TreeMap<>(PresortedComparable::slowCompare);
- for (T item : original.keySet()) {
- Set<T> rewrittenKeys = rewrite.apply(item);
- for (T rewrittenKey : rewrittenKeys) {
- result
- .computeIfAbsent(rewrittenKey, k -> ProgramMethodSet.create())
- .addAll(original.get(item));
- }
- }
+ private static SortedMap<DexMethod, ProgramMethodSet> rewriteInvokesWithContexts(
+ Map<DexMethod, ProgramMethodSet> invokes, GraphLense lens) {
+ SortedMap<DexMethod, ProgramMethodSet> result = new TreeMap<>(PresortedComparable::slowCompare);
+ invokes.forEach(
+ (method, contexts) ->
+ result
+ .computeIfAbsent(
+ lens.getRenamedMethodSignature(method), ignore -> ProgramMethodSet.create())
+ .addAll(contexts));
return Collections.unmodifiableSortedMap(result);
}
@@ -927,12 +918,14 @@
public boolean isAccessModificationAllowed(DexReference reference) {
assert options().getProguardConfiguration().isAccessModificationAllowed();
- return allowAccessModification.contains(reference) || !isPinned(reference);
+ return keepInfo
+ .getInfo(reference, this)
+ .isAccessModificationAllowed(options().getProguardConfiguration());
}
public boolean isPinned(DexReference reference) {
assert checkIfObsolete();
- return pinnedItems.contains(reference);
+ return keepInfo.isPinned(reference, this);
}
public boolean hasPinnedInstanceInitializer(DexType type) {
@@ -948,9 +941,8 @@
return false;
}
- public Set<DexReference> getPinnedItems() {
- assert checkIfObsolete();
- return pinnedItems;
+ public KeepInfoCollection getKeepInfo() {
+ return keepInfo;
}
/**
@@ -984,70 +976,62 @@
// Switchmap classes should never be affected by renaming.
assert lens.assertDefinitionsNotModified(
switchMaps.keySet().stream()
- .map(this::definitionFor)
- .filter(Objects::nonNull)
+ .map(this::resolveField)
+ .filter(FieldResolutionResult::isSuccessfulResolution)
+ .map(FieldResolutionResult::getResolvedField)
.collect(Collectors.toList()));
assert lens.assertDefinitionsNotModified(
neverMerge.stream()
.map(this::definitionFor)
- .filter(Objects::nonNull)
- .collect(Collectors.toList()));
-
- assert lens.assertDefinitionsNotModified(
- alwaysInline.stream()
- .map(this::definitionFor)
- .filter(Objects::nonNull)
+ .filter(AssertionUtils::assertNotNull)
.collect(Collectors.toList()));
return new AppInfoWithLiveness(
application,
deadProtoTypes,
missingTypes,
- rewriteItems(liveTypes, lens::lookupType),
- rewriteItems(instantiatedAppServices, lens::lookupType),
- lens.rewriteMethodsConservatively(targetedMethods),
- lens.rewriteMethodsConservatively(failedResolutionTargets),
- lens.rewriteMethodsConservatively(bootstrapMethods),
- lens.rewriteMethodsConservatively(methodsTargetedByInvokeDynamic),
- lens.rewriteMethodsConservatively(virtualMethodsTargetedByInvokeDirect),
- lens.rewriteMethodsConservatively(liveMethods),
+ lens.rewriteTypes(liveTypes),
+ lens.rewriteTypes(instantiatedAppServices),
+ lens.rewriteMethods(targetedMethods),
+ lens.rewriteMethods(failedResolutionTargets),
+ lens.rewriteMethods(bootstrapMethods),
+ lens.rewriteMethods(methodsTargetedByInvokeDynamic),
+ lens.rewriteMethods(virtualMethodsTargetedByInvokeDirect),
+ lens.rewriteMethods(liveMethods),
fieldAccessInfoCollection.rewrittenWithLens(application, lens),
objectAllocationInfoCollection.rewrittenWithLens(application, lens),
- rewriteKeysConservativelyWhileMergingValues(
- virtualInvokes, lens::lookupMethodInAllContexts),
- rewriteKeysConservativelyWhileMergingValues(
- interfaceInvokes, lens::lookupMethodInAllContexts),
- rewriteKeysConservativelyWhileMergingValues(superInvokes, lens::lookupMethodInAllContexts),
- rewriteKeysConservativelyWhileMergingValues(directInvokes, lens::lookupMethodInAllContexts),
- rewriteKeysConservativelyWhileMergingValues(staticInvokes, lens::lookupMethodInAllContexts),
+ rewriteInvokesWithContexts(virtualInvokes, lens),
+ rewriteInvokesWithContexts(interfaceInvokes, lens),
+ rewriteInvokesWithContexts(superInvokes, lens),
+ rewriteInvokesWithContexts(directInvokes, lens),
+ rewriteInvokesWithContexts(staticInvokes, lens),
// TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
// after second tree shaking.
callSites,
- lens.rewriteReferencesConservatively(pinnedItems),
- lens.rewriteReferencesConservatively(allowAccessModification),
- rewriteReferenceKeys(mayHaveSideEffects, lens::lookupReference),
- rewriteReferenceKeys(noSideEffects, lens::lookupReference),
- rewriteReferenceKeys(assumedValues, lens::lookupReference),
- lens.rewriteMethodsWithRenamedSignature(alwaysInline),
- lens.rewriteMethodsWithRenamedSignature(forceInline),
- lens.rewriteMethodsWithRenamedSignature(neverInline),
- lens.rewriteMethodsWithRenamedSignature(whyAreYouNotInlining),
- lens.rewriteMethodsWithRenamedSignature(keepConstantArguments),
- lens.rewriteMethodsWithRenamedSignature(keepUnusedArguments),
- lens.rewriteMethodsWithRenamedSignature(reprocess),
- lens.rewriteMethodsWithRenamedSignature(neverReprocess),
+ keepInfo.rewrite(lens),
+ lens.rewriteReferenceKeys(mayHaveSideEffects),
+ lens.rewriteReferenceKeys(noSideEffects),
+ lens.rewriteReferenceKeys(assumedValues),
+ lens.rewriteMethods(alwaysInline),
+ lens.rewriteMethods(forceInline),
+ lens.rewriteMethods(neverInline),
+ lens.rewriteMethods(whyAreYouNotInlining),
+ lens.rewriteMethods(keepConstantArguments),
+ lens.rewriteMethods(keepUnusedArguments),
+ lens.rewriteMethods(reprocess),
+ lens.rewriteMethods(neverReprocess),
alwaysClassInline.rewriteItems(lens::lookupType),
- rewriteItems(neverClassInline, lens::lookupType),
- rewriteItems(neverMerge, lens::lookupType),
- lens.rewriteReferencesConservatively(neverPropagateValue),
- lens.rewriteReferencesConservatively(identifierNameStrings),
+ lens.rewriteTypes(neverClassInline),
+ lens.rewriteTypes(neverMerge),
+ lens.rewriteReferences(neverPropagateValue),
+ lens.rewriteReferenceKeys(identifierNameStrings),
// Don't rewrite pruned types - the removed types are identified by their original name.
prunedTypes,
- rewriteReferenceKeys(switchMaps, lens::lookupField),
+ lens.rewriteFieldKeys(switchMaps),
enumValueInfoMaps.rewrittenWithLens(lens),
- rewriteItems(constClassReferences, lens::lookupType),
- rewriteReferenceKeys(initClassReferences, lens::lookupType));
+ lens.rewriteTypes(constClassReferences),
+ lens.rewriteTypeKeys(initClassReferences));
}
/**
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 8a54cca..a0a1924 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -92,7 +92,9 @@
import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction;
import com.android.tools.r8.shaking.EnqueuerWorklist.EnqueuerAction;
import com.android.tools.r8.shaking.GraphReporter.KeepReasonWitness;
+import com.android.tools.r8.shaking.KeepInfoCollection.MutableKeepInfoCollection;
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
+import com.android.tools.r8.shaking.RootSetBuilder.ItemsWithRules;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.ScopedDexMethodSet.AddMethodIfMoreVisibleResult;
import com.android.tools.r8.utils.Action;
@@ -237,7 +239,7 @@
private final Set<DexType> missingTypes = Sets.newIdentityHashSet();
/** Set of proto types that were found to be dead during the first round of tree shaking. */
- private Set<DexType> initialDeadProtoTypes;
+ private Set<DexType> initialDeadProtoTypes = Sets.newIdentityHashSet();
/** Set of types that were found to be missing during the first round of tree shaking. */
private Set<DexType> initialMissingTypes;
@@ -305,18 +307,8 @@
*/
private final Set<DexReference> reportedMissing = Sets.newIdentityHashSet();
- /**
- * A set of references that we are keeping due to keep rules. This may differ from the root set
- * due to dependent keep rules.
- */
- private final Set<DexReference> pinnedItems = Sets.newIdentityHashSet();
-
- /**
- * A set of references that we are keeping due to keep rules, which we are allowed to publicize.
- */
- // TODO(b/156715504): This should be maintained in a structure that describes what we are allowed
- // and not allowed to do with program items that are referenced from keep rules.
- private final Map<DexReference, OptionalBool> allowAccessModification = new IdentityHashMap<>();
+ /** Collection of keep requirements for the program. */
+ private final MutableKeepInfoCollection keepInfo = new MutableKeepInfoCollection();
/**
* A set of seen const-class references that both serve as an initial lock-candidate set and will
@@ -374,10 +366,15 @@
this.useRegistryFactory = createUseRegistryFactory();
this.workList = EnqueuerWorklist.createWorklist(appView);
- if (options.protoShrinking().enableGeneratedMessageLiteShrinking
- && mode.isInitialOrFinalTreeShaking()) {
- registerAnalysis(new ProtoEnqueuerExtension(appView));
+ if (mode.isInitialOrFinalTreeShaking()) {
+ if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
+ registerAnalysis(new ProtoEnqueuerExtension(appView));
+ }
+ appView.withGeneratedMessageLiteBuilderShrinker(
+ shrinker -> registerAnalysis(shrinker.createEnqueuerAnalysis()));
}
+
+
liveTypes = new SetWithReportedReason<>();
initializedTypes = new SetWithReportedReason<>();
targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
@@ -507,25 +504,6 @@
recordTypeReference(field.type);
}
- public DexDefinition definitionFor(DexReference reference) {
- if (reference.isDexType()) {
- return definitionFor(reference.asDexType());
- } else if (reference.isDexMethod()) {
- return definitionFor(reference.asDexMethod());
- } else {
- assert reference.isDexField();
- return definitionFor(reference.asDexField());
- }
- }
-
- public DexEncodedField definitionFor(DexField field) {
- DexClass clazz = definitionFor(field.holder);
- if (clazz == null) {
- return null;
- }
- return clazz.lookupField(field);
- }
-
public DexEncodedMethod definitionFor(DexMethod method) {
DexClass clazz = definitionFor(method.holder);
if (clazz == null) {
@@ -546,6 +524,10 @@
return clazz;
}
+ public boolean isPinned(DexType type) {
+ return keepInfo.isPinned(type, appInfo);
+ }
+
private void addLiveNonProgramType(DexClass clazz) {
assert clazz.isNotProgramClass();
// Fast path to avoid the worklist when the class is already seen.
@@ -614,24 +596,98 @@
}
}
- private static <T> SetWithReason<T> newSetWithoutReasonReporter() {
- return new SetWithReason<>((f, r) -> {});
- }
-
private void enqueueRootItems(Map<DexReference, Set<ProguardKeepRuleBase>> items) {
items.entrySet().forEach(this::enqueueRootItem);
}
+ private void enqueueRootItems(ItemsWithRules items) {
+ items.forEachField(this::enqueueRootField);
+ items.forEachMethod(this::enqueueRootMethod);
+ items.forEachClass(this::enqueueRootClass);
+ }
+
private void enqueueRootItem(Entry<DexReference, Set<ProguardKeepRuleBase>> root) {
- DexDefinition item = appView.definitionFor(root.getKey());
- if (item != null) {
- enqueueRootItem(item, root.getValue());
+ DexReference reference = root.getKey();
+ Set<ProguardKeepRuleBase> rules = root.getValue();
+ if (reference.isDexField()) {
+ enqueueRootField(reference.asDexField(), rules);
+ } else if (reference.isDexMethod()) {
+ enqueueRootMethod(reference.asDexMethod(), rules);
+ } else if (reference.isDexType()) {
+ enqueueRootClass(reference.asDexType(), rules);
} else {
- // TODO(b/123923324): Verify that root items are present.
- // assert false : "Expected root item `" + root.getKey().toSourceString() + "` to be present";
+ throw new Unreachable();
}
}
+ // TODO(b/123923324): Verify that root items are present.
+ private void enqueueRootClass(DexType type, Set<ProguardKeepRuleBase> rules) {
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ if (clazz != null) {
+ enqueueRootClass(clazz, rules, null);
+ }
+ }
+
+ private void enqueueRootClass(
+ DexProgramClass clazz, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
+ KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz);
+ keepClassWithRules(clazz, rules);
+ if (clazz.isAnnotation()) {
+ workList.enqueueMarkAnnotationInstantiatedAction(clazz, witness);
+ } else if (clazz.isInterface()) {
+ workList.enqueueMarkInterfaceInstantiatedAction(clazz, witness);
+ } else {
+ workList.enqueueMarkInstantiatedAction(clazz, null, InstantiationReason.KEEP_RULE, witness);
+ if (clazz.hasDefaultInitializer()) {
+ ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
+ if (forceProguardCompatibility) {
+ workList.enqueueMarkMethodKeptAction(
+ defaultInitializer,
+ graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
+ }
+ if (clazz.isExternalizable(appView)) {
+ enqueueMarkMethodLiveAction(defaultInitializer, witness);
+ }
+ }
+ }
+ }
+
+ // TODO(b/123923324): Verify that root items are present.
+ private void enqueueRootField(DexField reference, Set<ProguardKeepRuleBase> rules) {
+ DexProgramClass holder = getProgramClassOrNull(reference.holder);
+ if (holder != null) {
+ ProgramField field = holder.lookupProgramField(reference);
+ if (field != null) {
+ enqueueRootField(field, rules, null);
+ }
+ }
+ }
+
+ private void enqueueRootField(
+ ProgramField field, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
+ keepFieldWithRules(field.getHolder(), field.getDefinition(), rules);
+ workList.enqueueMarkFieldKeptAction(
+ field, graphReporter.reportKeepField(precondition, rules, field.getDefinition()));
+ }
+
+ // TODO(b/123923324): Verify that root items are present.
+ private void enqueueRootMethod(DexMethod reference, Set<ProguardKeepRuleBase> rules) {
+ DexProgramClass holder = getProgramClassOrNull(reference.holder);
+ if (holder != null) {
+ ProgramMethod method = holder.lookupProgramMethod(reference);
+ if (method != null) {
+ enqueueRootMethod(method, rules, null);
+ }
+ }
+ }
+
+ private void enqueueRootMethod(
+ ProgramMethod method, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
+ keepMethodWithRules(method.getHolder(), method.getDefinition(), rules);
+ workList.enqueueMarkMethodKeptAction(
+ method, graphReporter.reportKeepMethod(precondition, rules, method.getDefinition()));
+ }
+
private void enqueueRootItem(DexDefinition item, Set<ProguardKeepRuleBase> rules) {
internalEnqueueRootItem(item, rules, null);
}
@@ -640,45 +696,24 @@
DexDefinition item, Set<ProguardKeepRuleBase> rules, DexDefinition precondition) {
if (item.isDexClass()) {
DexProgramClass clazz = item.asDexClass().asProgramClass();
- KeepReasonWitness witness = graphReporter.reportKeepClass(precondition, rules, clazz);
- if (clazz.isAnnotation()) {
- workList.enqueueMarkAnnotationInstantiatedAction(clazz, witness);
- } else if (clazz.isInterface()) {
- workList.enqueueMarkInterfaceInstantiatedAction(clazz, witness);
- } else {
- workList.enqueueMarkInstantiatedAction(clazz, null, InstantiationReason.KEEP_RULE, witness);
- if (clazz.hasDefaultInitializer()) {
- ProgramMethod defaultInitializer = clazz.getProgramDefaultInitializer();
- if (forceProguardCompatibility) {
- workList.enqueueMarkMethodKeptAction(
- defaultInitializer,
- graphReporter.reportCompatKeepDefaultInitializer(defaultInitializer));
- }
- if (clazz.isExternalizable(appView)) {
- enqueueMarkMethodLiveAction(defaultInitializer, witness);
- }
- }
+ if (clazz != null) {
+ enqueueRootClass(clazz, rules, precondition);
}
} else if (item.isDexEncodedField()) {
DexEncodedField field = item.asDexEncodedField();
DexProgramClass holder = getProgramClassOrNull(field.holder());
if (holder != null) {
- workList.enqueueMarkFieldKeptAction(
- new ProgramField(holder, field),
- graphReporter.reportKeepField(precondition, rules, field));
+ enqueueRootField(new ProgramField(holder, field), rules, precondition);
}
} else if (item.isDexEncodedMethod()) {
- DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
- DexProgramClass holder = getProgramClassOrNull(encodedMethod.holder());
+ DexEncodedMethod method = item.asDexEncodedMethod();
+ DexProgramClass holder = getProgramClassOrNull(method.holder());
if (holder != null) {
- workList.enqueueMarkMethodKeptAction(
- new ProgramMethod(holder, encodedMethod),
- graphReporter.reportKeepMethod(precondition, rules, encodedMethod));
+ enqueueRootMethod(new ProgramMethod(holder, method), rules, precondition);
}
} else {
throw new IllegalArgumentException(item.toString());
}
- addPinnedItem(item.toReference(), rules);
}
private void enqueueFirstNonSerializableClassInitializer(
@@ -1314,7 +1349,7 @@
boolean skipTracing =
appView.withGeneratedExtensionRegistryShrinker(
shrinker ->
- shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, pinnedItems),
+ shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo),
false);
if (skipTracing) {
addDeadProtoTypeCandidate(field.getHolder());
@@ -1373,7 +1408,7 @@
boolean skipTracing =
appView.withGeneratedExtensionRegistryShrinker(
shrinker ->
- shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, pinnedItems),
+ shrinker.isDeadProtoExtensionField(field, fieldAccessInfoCollection, keepInfo),
false);
if (skipTracing) {
addDeadProtoTypeCandidate(field.getHolder());
@@ -1482,7 +1517,7 @@
recordTypeReference(innerClassAttribute.getInner());
recordTypeReference(innerClassAttribute.getOuter());
}
- EnclosingMethodAttribute enclosingMethodAttribute = holder.getEnclosingMethod();
+ EnclosingMethodAttribute enclosingMethodAttribute = holder.getEnclosingMethodAttribute();
if (enclosingMethodAttribute != null) {
DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
if (enclosingMethod != null) {
@@ -1619,8 +1654,8 @@
}
private void enqueueHolderWithDependentInstanceConstructor(
- DexProgramClass clazz, ProgramMethod instanceInitializer, Set<ProguardKeepRuleBase> reasons) {
- enqueueRootItem(clazz, reasons);
+ ProgramMethod instanceInitializer, Set<ProguardKeepRuleBase> reasons) {
+ enqueueRootItem(instanceInitializer.getHolder(), reasons);
}
private void processAnnotations(DexProgramClass holder, DexDefinition annotatedItem) {
@@ -1808,12 +1843,12 @@
holder,
(dexType, ignored) -> {
if (holder.isProgramClass()) {
- DexReference holderReference = holder.toReference();
- addPinnedItem(holderReference);
- rootSet.shouldNotBeMinified(holderReference);
+ DexProgramClass holderClass = holder.asProgramClass();
+ keepInfo.keepClass(holderClass);
+ rootSet.shouldNotBeMinified(holder.toReference());
for (DexEncodedMember<?, ?> member : holder.members()) {
+ keepInfo.keepMember(holderClass, member);
DexMember<?, ?> memberReference = member.toReference();
- addPinnedItem(memberReference);
rootSet.shouldNotBeMinified(memberReference);
}
}
@@ -1841,7 +1876,7 @@
private void reportMissingClass(DexType clazz) {
assert !mode.isFinalTreeShaking()
|| appView.dexItemFactory().isPossiblyCompilerSynthesizedType(clazz)
- || (initialDeadProtoTypes != null && initialDeadProtoTypes.contains(clazz))
+ || initialDeadProtoTypes.contains(clazz)
|| initialMissingTypes.contains(clazz)
: "Unexpected missing class `" + clazz.toSourceString() + "`";
boolean newReport = missingTypes.add(clazz);
@@ -2461,7 +2496,7 @@
(type, subTypeConsumer, lambdaConsumer) ->
objectAllocationInfoCollection.forEachInstantiatedSubType(
type, subTypeConsumer, lambdaConsumer, appInfo),
- pinnedItems::contains)
+ reference -> keepInfo.isPinned(reference, appInfo))
.forEach(
target ->
markVirtualDispatchTargetAsLive(
@@ -2528,7 +2563,7 @@
// TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
// marking for not renaming it is in the root set.
workList.enqueueMarkMethodKeptAction(new ProgramMethod(clazz, valuesMethod), reason);
- addPinnedItem(valuesMethod.toReference());
+ keepInfo.keepMethod(clazz, valuesMethod);
rootSet.shouldNotBeMinified(valuesMethod.toReference());
}
}
@@ -2599,9 +2634,10 @@
this.dontWarnPatterns = dontWarnPatterns;
// Translate the result of root-set computation into enqueuer actions.
if (appView.options().getProguardConfiguration() != null
- && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations
- && mode.isInitialTreeShaking()) {
- registerAnalysis(new KotlinMetadataEnqueuerExtension(appView, enqueuerDefinitionSupplier));
+ && !options.kotlinOptimizationOptions().disableKotlinSpecificOptimizations) {
+ registerAnalysis(
+ new KotlinMetadataEnqueuerExtension(
+ appView, enqueuerDefinitionSupplier, initialPrunedTypes));
}
if (appView.options().isShrinking() || appView.options().getProguardConfiguration() == null) {
enqueueRootItems(rootSet.noShrinking);
@@ -2632,37 +2668,44 @@
return appInfoWithLiveness;
}
- public boolean isPinned(DexReference reference) {
- return pinnedItems.contains(reference);
+ private void keepClassWithRules(DexProgramClass clazz, Set<ProguardKeepRuleBase> rules) {
+ keepInfo.joinClass(
+ clazz,
+ info ->
+ info.pin()
+ .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
}
- private boolean addPinnedItem(DexReference reference) {
- allowAccessModification.put(reference, OptionalBool.unknown());
- return pinnedItems.add(reference);
+ private void keepMethodWithRules(
+ DexProgramClass holder, DexEncodedMethod method, Set<ProguardKeepRuleBase> rules) {
+ keepInfo.joinMethod(
+ holder,
+ method,
+ info ->
+ info.pin()
+ .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
}
- private boolean addPinnedItem(DexReference reference, Set<ProguardKeepRuleBase> rules) {
- assert rules != null;
- assert !rules.isEmpty();
- OptionalBool allowAccessModificationOfReference =
- allowAccessModification.getOrDefault(reference, OptionalBool.TRUE);
- if (allowAccessModificationOfReference.isTrue()) {
- for (ProguardKeepRuleBase rule : rules) {
- ProguardKeepRuleModifiers modifiers =
- (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
- .getModifiers();
- if (!modifiers.allowsAccessModification) {
- allowAccessModificationOfReference = OptionalBool.FALSE;
- break;
- }
+ private void keepFieldWithRules(
+ DexProgramClass holder, DexEncodedField field, Set<ProguardKeepRuleBase> rules) {
+ keepInfo.joinField(
+ holder,
+ field,
+ info ->
+ info.pin()
+ .lazyDisallowAccessModification(() -> computeDisallowAccessModification(rules)));
+ }
+
+ private boolean computeDisallowAccessModification(Set<ProguardKeepRuleBase> rules) {
+ for (ProguardKeepRuleBase rule : rules) {
+ ProguardKeepRuleModifiers modifiers =
+ (rule.isProguardIfRule() ? rule.asProguardIfRule().getSubsequentRule() : rule)
+ .getModifiers();
+ if (!modifiers.allowsAccessModification) {
+ return true;
}
- allowAccessModification.put(reference, allowAccessModificationOfReference);
}
- return pinnedItems.add(reference);
- }
-
- public boolean isMissing(DexType type) {
- return missingTypes.contains(type);
+ return false;
}
private static class SyntheticAdditions {
@@ -2674,15 +2717,16 @@
Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
- // Subset of live methods that need to be pinned.
- Set<DexMethod> pinnedMethods = Sets.newIdentityHashSet();
+ // Subset of live methods that need have keep requirements.
+ List<Pair<ProgramMethod, Consumer<KeepMethodInfo.Joiner>>> liveMethodsWithKeepActions =
+ new ArrayList<>();
// Subset of synthesized classes that need to be added to the main-dex file.
Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
boolean isEmpty() {
boolean empty = syntheticInstantiations.isEmpty() && liveMethods.isEmpty();
- assert !empty || (pinnedMethods.isEmpty() && mainDexTypes.isEmpty());
+ assert !empty || (liveMethodsWithKeepActions.isEmpty() && mainDexTypes.isEmpty());
return empty;
}
@@ -2706,9 +2750,10 @@
liveMethods.put(signature, method);
}
- void addLiveAndPinnedMethod(ProgramMethod method) {
+ void addLiveMethodWithKeepAction(
+ ProgramMethod method, Consumer<KeepMethodInfo.Joiner> keepAction) {
addLiveMethod(method);
- pinnedMethods.add(method.getDefinition().method);
+ liveMethodsWithKeepActions.add(new Pair<>(method, keepAction));
}
void amendApplication(Builder appBuilder) {
@@ -2727,7 +2772,8 @@
// All synthetic additions are initial tree shaking only. No need to track keep reasons.
KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
- pinnedMethods.forEach(enqueuer::addPinnedItem);
+ liveMethodsWithKeepActions.forEach(
+ item -> enqueuer.keepInfo.joinMethod(item.getFirst(), item.getSecond()));
for (Pair<DexProgramClass, ProgramMethod> clazzAndContext :
syntheticInstantiations.values()) {
enqueuer.workList.enqueueMarkInstantiatedAction(
@@ -2777,7 +2823,7 @@
DexProgramClass holder = bridge.getHolder();
DexEncodedMethod method = bridge.getDefinition();
holder.addVirtualMethod(method);
- additions.addLiveAndPinnedMethod(bridge);
+ additions.addLiveMethodWithKeepAction(bridge, KeepMethodInfo.Joiner::pin);
}
syntheticInterfaceMethodBridges.clear();
}
@@ -2842,11 +2888,12 @@
// to a static method will invalidate the reachable method sets for tracing methods.
ensureLambdaAccessibility();
- // Remove the items from `allowAccessModification` that we are not allowed to publicize.
- allowAccessModification.entrySet().removeIf(entry -> !entry.getValue().isTrue());
-
// Compute the set of dead proto types.
deadProtoTypeCandidates.removeIf(this::isTypeLive);
+ Set<DexType> deadProtoTypes =
+ SetUtils.newIdentityHashSet(deadProtoTypeCandidates.size() + initialDeadProtoTypes.size());
+ deadProtoTypeCandidates.forEach(deadProtoType -> deadProtoTypes.add(deadProtoType.type));
+ deadProtoTypes.addAll(initialDeadProtoTypes);
// Remove the temporary mappings that have been inserted into the field access info collection
// and verify that the mapping is then one-to-one.
@@ -2891,7 +2938,7 @@
AppInfoWithLiveness appInfoWithLiveness =
new AppInfoWithLiveness(
app,
- SetUtils.mapIdentityHashSet(deadProtoTypeCandidates, DexProgramClass::getType),
+ deadProtoTypes,
mode.isFinalTreeShaking()
? Sets.union(initialMissingTypes, missingTypes)
: missingTypes,
@@ -2914,8 +2961,7 @@
toImmutableSortedMap(directInvokes, PresortedComparable::slowCompare),
toImmutableSortedMap(staticInvokes, PresortedComparable::slowCompare),
callSites,
- pinnedItems,
- allowAccessModification.keySet(),
+ keepInfo,
rootSet.mayHaveSideEffects,
rootSet.noSideEffects,
rootSet.assumedValues,
@@ -3258,9 +3304,9 @@
});
consequentRootSet.forEachMemberWithDependentItems(
appView,
- member -> {
+ (member, dependentItems) -> {
if (isMemberLive(member)) {
- enqueueRootItems(consequentRootSet.getDependentItems(member));
+ enqueueRootItems(dependentItems);
}
});
// TODO(b/132600955): This modifies the root set. Should the consequent be persistent?
@@ -3295,7 +3341,7 @@
ProgramMethod methodToKeep = action.getMethodToKeep();
ProgramMethod singleTarget = action.getSingleTarget();
DexEncodedMethod singleTargetMethod = singleTarget.getDefinition();
- if (rootSet.noShrinking.containsKey(singleTargetMethod.method)) {
+ if (rootSet.noShrinking.containsMethod(singleTarget.getReference())) {
return;
}
if (methodToKeep != singleTarget) {
@@ -3319,11 +3365,12 @@
action.getAction().accept(builder);
}
+ // TODO(b/157700141): Determine if this is the right way to avoid modification of pinned lambdas.
private void unpinLambdaMethods() {
assert desugaredLambdaImplementationMethods.isEmpty()
|| options.desugarState == DesugarState.ON;
for (DexMethod method : desugaredLambdaImplementationMethods) {
- pinnedItems.remove(method);
+ keepInfo.unsafeUnpinMethod(method);
rootSet.prune(method);
}
desugaredLambdaImplementationMethods.clear();
@@ -3610,7 +3657,8 @@
workList.enqueueMarkInstantiatedAction(
clazz, null, InstantiationReason.REFLECTION, KeepReason.reflectiveUseIn(method));
}
- if (addPinnedItem(encodedField.field)) {
+ if (!keepInfo.getFieldInfo(encodedField, clazz).isPinned()) {
+ keepInfo.pinField(clazz, encodedField);
markFieldAsKept(new ProgramField(clazz, encodedField), KeepReason.reflectiveUseIn(method));
}
} else {
@@ -3797,14 +3845,14 @@
// Add this interface to the set of pinned items to ensure that we do not merge the
// interface into its unique subtype, if any.
// TODO(b/145344105): This should be superseded by the unknown interface hierarchy.
- addPinnedItem(clazz.type);
+ keepInfo.pinClass(clazz);
KeepReason reason = KeepReason.reflectiveUseIn(method);
markInterfaceAsInstantiated(clazz, graphReporter.registerClass(clazz, reason));
// Also pin all of its virtual methods to ensure that the devirtualizer does not perform
// illegal rewritings of invoke-interface instructions into invoke-virtual instructions.
for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
- addPinnedItem(virtualMethod.method);
+ keepInfo.pinMethod(clazz, virtualMethod);
markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
}
}
@@ -4113,16 +4161,7 @@
this.enqueuer = enqueuer;
}
- @Override
- public DexDefinition definitionFor(DexReference reference) {
- return enqueuer.definitionFor(reference);
- }
-
- @Override
- public DexEncodedField definitionFor(DexField field) {
- return enqueuer.definitionFor(field);
- }
-
+ @Deprecated
@Override
public DexEncodedMethod definitionFor(DexMethod method) {
return enqueuer.definitionFor(method);
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerMetadataTraceable.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerMetadataTraceable.java
new file mode 100644
index 0000000..e17e571
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerMetadataTraceable.java
@@ -0,0 +1,12 @@
+// 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.shaking;
+
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+
+public interface EnqueuerMetadataTraceable {
+
+ void trace(DexDefinitionSupplier definitionSupplier);
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
new file mode 100644
index 0000000..58367a9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -0,0 +1,93 @@
+// 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.shaking;
+
+/** Immutable keep requirements for a class. */
+public final class KeepClassInfo extends KeepInfo<KeepClassInfo.Builder, KeepClassInfo> {
+
+ // Requires all aspects of a class to be kept.
+ private static final KeepClassInfo TOP = new Builder().makeTop().build();
+
+ // Requires no aspects of a class to be kept.
+ private static final KeepClassInfo BOTTOM = new Builder().makeBottom().build();
+
+ public static KeepClassInfo top() {
+ return TOP;
+ }
+
+ public static KeepClassInfo bottom() {
+ return BOTTOM;
+ }
+
+ private KeepClassInfo(Builder builder) {
+ super(builder);
+ }
+
+ private Builder builder() {
+ return new Builder(this);
+ }
+
+ public Joiner joiner() {
+ assert !isTop();
+ return new Joiner(this);
+ }
+
+ @Override
+ public boolean isTop() {
+ return this.equals(top());
+ }
+
+ @Override
+ public boolean isBottom() {
+ return this.equals(bottom());
+ }
+
+ public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> {
+
+ private Builder() {
+ super();
+ }
+
+ private Builder(KeepClassInfo original) {
+ super(original);
+ }
+
+ @Override
+ public KeepClassInfo getTopInfo() {
+ return TOP;
+ }
+
+ @Override
+ public KeepClassInfo getBottomInfo() {
+ return BOTTOM;
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public boolean isEqualTo(KeepClassInfo other) {
+ return true;
+ }
+
+ @Override
+ public KeepClassInfo doBuild() {
+ return new KeepClassInfo(this);
+ }
+ }
+
+ public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepClassInfo> {
+
+ public Joiner(KeepClassInfo info) {
+ super(info.builder());
+ }
+
+ @Override
+ Joiner self() {
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
new file mode 100644
index 0000000..6f22db4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepFieldInfo.java
@@ -0,0 +1,95 @@
+// 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.shaking;
+
+/** Immutable keep requirements for a field. */
+public final class KeepFieldInfo extends KeepInfo<KeepFieldInfo.Builder, KeepFieldInfo> {
+
+ // Requires all aspects of a field to be kept.
+ private static final KeepFieldInfo TOP = new Builder().makeTop().build();
+
+ // Requires no aspects of a field to be kept.
+ private static final KeepFieldInfo BOTTOM = new Builder().makeBottom().build();
+
+ public static KeepFieldInfo top() {
+ return TOP;
+ }
+
+ public static KeepFieldInfo bottom() {
+ return BOTTOM;
+ }
+
+ private KeepFieldInfo(Builder builder) {
+ super(builder);
+ }
+
+ // This builder is not private as there are known instances where it is safe to modify keep info
+ // in a non-upwards direction.
+ Builder builder() {
+ return new Builder(this);
+ }
+
+ public Joiner joiner() {
+ assert !isTop();
+ return new Joiner(this);
+ }
+
+ @Override
+ public boolean isTop() {
+ return this.equals(top());
+ }
+
+ @Override
+ public boolean isBottom() {
+ return this.equals(bottom());
+ }
+
+ public static class Builder extends KeepInfo.Builder<Builder, KeepFieldInfo> {
+
+ private Builder() {
+ super();
+ }
+
+ private Builder(KeepFieldInfo original) {
+ super(original);
+ }
+
+ @Override
+ public KeepFieldInfo getTopInfo() {
+ return TOP;
+ }
+
+ @Override
+ public KeepFieldInfo getBottomInfo() {
+ return BOTTOM;
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public boolean isEqualTo(KeepFieldInfo other) {
+ return true;
+ }
+
+ @Override
+ public KeepFieldInfo doBuild() {
+ return new KeepFieldInfo(this);
+ }
+ }
+
+ public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepFieldInfo> {
+
+ public Joiner(KeepFieldInfo info) {
+ super(info.builder());
+ }
+
+ @Override
+ Joiner self() {
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
new file mode 100644
index 0000000..761547a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -0,0 +1,194 @@
+// 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.shaking;
+
+import com.android.tools.r8.shaking.KeepInfo.Builder;
+import java.util.function.Supplier;
+
+/** Keep information that can be associated with any item, i.e., class, method or field. */
+public abstract class KeepInfo<B extends Builder, K extends KeepInfo> {
+
+ private final boolean pinned;
+ private final boolean allowAccessModification;
+
+ private KeepInfo(boolean pinned, boolean allowAccessModification) {
+ this.pinned = pinned;
+ this.allowAccessModification = allowAccessModification;
+ }
+
+ KeepInfo(B builder) {
+ this(builder.isPinned(), builder.isAccessModificationAllowed());
+ }
+
+ /** True if an item must be present in the output. */
+ public boolean isPinned() {
+ return pinned;
+ }
+
+ /**
+ * True if an item may have its access flags modified.
+ *
+ * <p>This method requires knowledge of the global access modification as that will override the
+ * concrete value on a given item.
+ *
+ * @param configuration Global configuration object to determine access modification.
+ */
+ public boolean isAccessModificationAllowed(ProguardConfiguration configuration) {
+ return configuration.isAccessModificationAllowed() && internalIsAccessModificationAllowed();
+ }
+
+ // Internal accessor for the items access-modification bit.
+ boolean internalIsAccessModificationAllowed() {
+ return allowAccessModification;
+ }
+
+ public abstract boolean isTop();
+
+ public abstract boolean isBottom();
+
+ public boolean isLessThanOrEquals(K other) {
+ // An item is less, aka, lower in the lattice, if each of its attributes is at least as
+ // permissive of that on other.
+ return (!pinned || other.isPinned())
+ && (allowAccessModification || !other.internalIsAccessModificationAllowed());
+ }
+
+ /** Builder to construct an arbitrary keep info object. */
+ public abstract static class Builder<B extends Builder, K extends KeepInfo> {
+
+ abstract B self();
+
+ abstract K doBuild();
+
+ abstract K getTopInfo();
+
+ abstract K getBottomInfo();
+
+ abstract boolean isEqualTo(K other);
+
+ private K original;
+ private boolean pinned;
+ private boolean allowAccessModification;
+
+ Builder() {
+ // Default initialized. Use should be followed by makeTop/makeBottom.
+ }
+
+ Builder(K original) {
+ this.original = original;
+ pinned = original.isPinned();
+ allowAccessModification = original.internalIsAccessModificationAllowed();
+ }
+
+ B makeTop() {
+ pin();
+ disallowAccessModification();
+ return self();
+ }
+
+ B makeBottom() {
+ unpin();
+ allowAccessModification();
+ return self();
+ }
+
+ public K build() {
+ if (original != null) {
+ if (internalIsEqualTo(original)) {
+ return original;
+ }
+ if (internalIsEqualTo(getTopInfo())) {
+ return getTopInfo();
+ }
+ if (internalIsEqualTo(getBottomInfo())) {
+ return getBottomInfo();
+ }
+ }
+ return doBuild();
+ }
+
+ private boolean internalIsEqualTo(K other) {
+ return isPinned() == other.isPinned()
+ && isAccessModificationAllowed() == other.internalIsAccessModificationAllowed()
+ && isEqualTo(other);
+ }
+
+ public boolean isPinned() {
+ return pinned;
+ }
+
+ public boolean isAccessModificationAllowed() {
+ return allowAccessModification;
+ }
+
+ public B setPinned(boolean pinned) {
+ this.pinned = pinned;
+ return self();
+ }
+
+ public B pin() {
+ return setPinned(true);
+ }
+
+ public B unpin() {
+ return setPinned(false);
+ }
+
+ public B setAllowAccessModification(boolean allowAccessModification) {
+ this.allowAccessModification = allowAccessModification;
+ return self();
+ }
+
+ public B allowAccessModification() {
+ return setAllowAccessModification(true);
+ }
+
+ public B disallowAccessModification() {
+ return setAllowAccessModification(false);
+ }
+ }
+
+ /** Joiner to construct monotonically increasing keep info object. */
+ public abstract static class Joiner<
+ J extends Joiner, B extends Builder, K extends KeepInfo<B, K>> {
+
+ abstract J self();
+
+ private final Builder<B, K> builder;
+
+ Joiner(Builder<B, K> builder) {
+ this.builder = builder;
+ }
+
+ public boolean isTop() {
+ return builder.isEqualTo(builder.getTopInfo());
+ }
+
+ public J top() {
+ builder.makeTop();
+ return self();
+ }
+
+ public J pin() {
+ builder.pin();
+ return self();
+ }
+
+ // Lazy modification of access modification.
+ // Only forced if access modification is still allowed.
+ public J lazyDisallowAccessModification(Supplier<Boolean> lazyShouldDisallow) {
+ if (builder.isAccessModificationAllowed() && lazyShouldDisallow.get()) {
+ builder.disallowAccessModification();
+ }
+ return self();
+ }
+
+ public K join() {
+ K joined = builder.build();
+ K original = builder.original;
+ assert original.isLessThanOrEquals(joined);
+ return joined;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
new file mode 100644
index 0000000..37c8771
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -0,0 +1,368 @@
+// 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.shaking;
+
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMember;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.KeepFieldInfo.Joiner;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+// Non-mutable collection of keep information pertaining to a program.
+public abstract class KeepInfoCollection {
+
+ // TODO(b/157538235): This should not be bottom.
+ private static KeepClassInfo keepInfoForNonProgramClass() {
+ return KeepClassInfo.bottom();
+ }
+
+ // TODO(b/157538235): This should not be bottom.
+ private static KeepMethodInfo keepInfoForNonProgramMethod() {
+ return KeepMethodInfo.bottom();
+ }
+
+ // TODO(b/157538235): This should not be bottom.
+ private static KeepFieldInfo keepInfoForNonProgramField() {
+ return KeepFieldInfo.bottom();
+ }
+
+ /**
+ * Base accessor for keep info on a class.
+ *
+ * <p>Access may never be granted directly on DexType as the "keep info" for any non-program type
+ * is not the same as the default keep info for a program type. By typing the interface at program
+ * item we can eliminate errors where a reference to a non-program item results in optimizations
+ * assuming aspects of it can be changed when in fact they can not.
+ */
+ public abstract KeepClassInfo getClassInfo(DexProgramClass clazz);
+
+ /**
+ * Base accessor for keep info on a method.
+ *
+ * <p>See comment on class access for why this is typed at program method.
+ */
+ public abstract KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder);
+
+ /**
+ * Base accessor for keep info on a field.
+ *
+ * <p>See comment on class access for why this is typed at program field.
+ */
+ public abstract KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder);
+
+ public final KeepClassInfo getClassInfo(DexType type, DexDefinitionSupplier definitions) {
+ DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type));
+ return clazz == null ? keepInfoForNonProgramClass() : getClassInfo(clazz);
+ }
+
+ public final KeepMethodInfo getMethodInfo(ProgramMethod method) {
+ return getMethodInfo(method.getDefinition(), method.getHolder());
+ }
+
+ public final KeepMethodInfo getMethodInfo(DexMethod method, DexDefinitionSupplier definitions) {
+ DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(method.holder));
+ if (holder == null) {
+ return keepInfoForNonProgramMethod();
+ }
+ DexEncodedMethod definition = holder.lookupMethod(method);
+ return definition == null ? KeepMethodInfo.bottom() : getMethodInfo(definition, holder);
+ }
+
+ public final KeepFieldInfo getFieldInfo(ProgramField field) {
+ return getFieldInfo(field.getDefinition(), field.getHolder());
+ }
+
+ public final KeepFieldInfo getFieldInfo(DexField field, DexDefinitionSupplier definitions) {
+ DexProgramClass holder = asProgramClassOrNull(definitions.definitionFor(field.holder));
+ if (holder == null) {
+ return keepInfoForNonProgramField();
+ }
+ DexEncodedField definition = holder.lookupField(field);
+ return definition == null ? KeepFieldInfo.bottom() : getFieldInfo(definition, holder);
+ }
+
+ public final KeepInfo getInfo(DexReference reference, DexDefinitionSupplier definitions) {
+ if (reference.isDexType()) {
+ return getClassInfo(reference.asDexType(), definitions);
+ }
+ if (reference.isDexMethod()) {
+ return getMethodInfo(reference.asDexMethod(), definitions);
+ }
+ if (reference.isDexField()) {
+ return getFieldInfo(reference.asDexField(), definitions);
+ }
+ throw new Unreachable();
+ }
+
+ public final boolean isPinned(DexReference reference, DexDefinitionSupplier definitions) {
+ return getInfo(reference, definitions).isPinned();
+ }
+
+ public final boolean isPinned(DexType type, DexDefinitionSupplier definitions) {
+ return getClassInfo(type, definitions).isPinned();
+ }
+
+ public final boolean isPinned(DexMethod method, DexDefinitionSupplier definitions) {
+ return getMethodInfo(method, definitions).isPinned();
+ }
+
+ public final boolean isPinned(DexField field, DexDefinitionSupplier definitions) {
+ return getFieldInfo(field, definitions).isPinned();
+ }
+
+ public final boolean verifyNoneArePinned(Collection<DexType> types, AppInfo appInfo) {
+ for (DexType type : types) {
+ DexProgramClass clazz =
+ asProgramClassOrNull(appInfo.definitionForWithoutExistenceAssert(type));
+ assert clazz == null || !getClassInfo(clazz).isPinned();
+ }
+ return true;
+ }
+
+ // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
+ @Deprecated
+ public abstract void forEachPinnedType(Consumer<DexType> consumer);
+
+ // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
+ @Deprecated
+ public abstract void forEachPinnedMethod(Consumer<DexMethod> consumer);
+
+ // TODO(b/156715504): We should try to avoid the need for iterating pinned items.
+ @Deprecated
+ public abstract void forEachPinnedField(Consumer<DexField> consumer);
+
+ public abstract KeepInfoCollection rewrite(NestedGraphLense lens);
+
+ public abstract KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator);
+
+ // Mutation interface for building up the keep info.
+ public static class MutableKeepInfoCollection extends KeepInfoCollection {
+
+ // These are typed at signatures but the interface should make sure never to allow access
+ // directly with a signature. See the comment in KeepInfoCollection.
+ private final Map<DexType, KeepClassInfo> keepClassInfo;
+ private final Map<DexMethod, KeepMethodInfo> keepMethodInfo;
+ private final Map<DexField, KeepFieldInfo> keepFieldInfo;
+
+ MutableKeepInfoCollection() {
+ this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
+ }
+
+ private MutableKeepInfoCollection(
+ Map<DexType, KeepClassInfo> keepClassInfo,
+ Map<DexMethod, KeepMethodInfo> keepMethodInfo,
+ Map<DexField, KeepFieldInfo> keepFieldInfo) {
+ this.keepClassInfo = keepClassInfo;
+ this.keepMethodInfo = keepMethodInfo;
+ this.keepFieldInfo = keepFieldInfo;
+ }
+
+ @Override
+ public KeepInfoCollection rewrite(NestedGraphLense lens) {
+ Map<DexType, KeepClassInfo> newClassInfo = new IdentityHashMap<>(keepClassInfo.size());
+ keepClassInfo.forEach(
+ (type, info) -> {
+ DexType newType = lens.lookupType(type);
+ assert !info.isPinned() || type == newType;
+ newClassInfo.put(newType, info);
+ });
+ Map<DexMethod, KeepMethodInfo> newMethodInfo = new IdentityHashMap<>(keepMethodInfo.size());
+ keepMethodInfo.forEach(
+ (method, info) -> {
+ DexMethod newMethod = lens.getRenamedMethodSignature(method);
+ assert !info.isPinned() || method == newMethod;
+ newMethodInfo.put(newMethod, info);
+ });
+ Map<DexField, KeepFieldInfo> newFieldInfo = new IdentityHashMap<>(keepFieldInfo.size());
+ keepFieldInfo.forEach(
+ (field, info) -> {
+ DexField newField = lens.getRenamedFieldSignature(field);
+ assert !info.isPinned() || field == newField;
+ newFieldInfo.put(newField, info);
+ });
+ return new MutableKeepInfoCollection(newClassInfo, newMethodInfo, newFieldInfo);
+ }
+
+ @Override
+ public KeepClassInfo getClassInfo(DexProgramClass clazz) {
+ return keepClassInfo.getOrDefault(clazz.type, KeepClassInfo.bottom());
+ }
+
+ @Override
+ public KeepMethodInfo getMethodInfo(DexEncodedMethod method, DexProgramClass holder) {
+ assert method.holder() == holder.type;
+ return keepMethodInfo.getOrDefault(method.method, KeepMethodInfo.bottom());
+ }
+
+ @Override
+ public KeepFieldInfo getFieldInfo(DexEncodedField field, DexProgramClass holder) {
+ assert field.holder() == holder.type;
+ return keepFieldInfo.getOrDefault(field.field, KeepFieldInfo.bottom());
+ }
+
+ public void joinClass(DexProgramClass clazz, Consumer<KeepClassInfo.Joiner> fn) {
+ KeepClassInfo info = getClassInfo(clazz);
+ if (info.isTop()) {
+ return;
+ }
+ KeepClassInfo.Joiner joiner = info.joiner();
+ fn.accept(joiner);
+ KeepClassInfo joined = joiner.join();
+ if (!info.equals(joined)) {
+ keepClassInfo.put(clazz.type, joined);
+ }
+ }
+
+ public void keepClass(DexProgramClass clazz) {
+ joinClass(clazz, KeepInfo.Joiner::top);
+ }
+
+ public void pinClass(DexProgramClass clazz) {
+ joinClass(clazz, KeepInfo.Joiner::pin);
+ }
+
+ public void joinMethod(
+ DexProgramClass holder, DexEncodedMethod method, Consumer<KeepMethodInfo.Joiner> fn) {
+ KeepMethodInfo info = getMethodInfo(method, holder);
+ if (info == KeepMethodInfo.top()) {
+ return;
+ }
+ KeepMethodInfo.Joiner joiner = info.joiner();
+ fn.accept(joiner);
+ KeepMethodInfo joined = joiner.join();
+ if (!info.equals(joined)) {
+ keepMethodInfo.put(method.method, joined);
+ }
+ }
+
+ public void joinMethod(ProgramMethod programMethod, Consumer<KeepMethodInfo.Joiner> fn) {
+ joinMethod(programMethod.getHolder(), programMethod.getDefinition(), fn);
+ }
+
+ public void keepMethod(ProgramMethod programMethod) {
+ keepMethod(programMethod.getHolder(), programMethod.getDefinition());
+ }
+
+ public void keepMethod(DexProgramClass holder, DexEncodedMethod method) {
+ joinMethod(holder, method, KeepInfo.Joiner::top);
+ }
+
+ public void pinMethod(DexProgramClass holder, DexEncodedMethod method) {
+ joinMethod(holder, method, KeepInfo.Joiner::pin);
+ }
+
+ // Unpinning a method represents a non-monotonic change to the keep info of that item.
+ // This is generally unsound as it requires additional analysis to determine that a method that
+ // was pinned no longer is. A known sound example is the enum analysis that will identify
+ // non-escaping enums on enum types that are not pinned, thus their methods do not need to be
+ // retained even if a rule has marked them as conditionally pinned.
+ public void unsafeUnpinMethod(ProgramMethod method) {
+ // This asserts that the holder is not pinned as some analysis must have established that the
+ // type is not "present" and thus the method need not be pinned.
+ assert !getClassInfo(method.getHolder()).isPinned();
+ unsafeUnpinMethod(method.getReference());
+ }
+
+ // TODO(b/157700141): Avoid pinning/unpinning references.
+ @Deprecated
+ public void unsafeUnpinMethod(DexMethod method) {
+ KeepMethodInfo info = keepMethodInfo.get(method);
+ if (info != null && info.isPinned()) {
+ keepMethodInfo.put(method, info.builder().unpin().build());
+ }
+ }
+
+ public void joinField(
+ DexProgramClass holder, DexEncodedField field, Consumer<KeepFieldInfo.Joiner> fn) {
+ KeepFieldInfo info = getFieldInfo(field, holder);
+ if (info.isTop()) {
+ return;
+ }
+ Joiner joiner = info.joiner();
+ fn.accept(joiner);
+ KeepFieldInfo joined = joiner.join();
+ if (!info.equals(joined)) {
+ keepFieldInfo.put(field.field, joined);
+ }
+ }
+
+ public void keepField(DexProgramClass holder, DexEncodedField field) {
+ joinField(holder, field, KeepInfo.Joiner::top);
+ }
+
+ public void pinField(DexProgramClass holder, DexEncodedField field) {
+ joinField(holder, field, KeepInfo.Joiner::pin);
+ }
+
+ public void keepMember(DexProgramClass holder, DexEncodedMember<?, ?> member) {
+ if (member.isDexEncodedMethod()) {
+ keepMethod(holder, member.asDexEncodedMethod());
+ } else {
+ assert member.isDexEncodedField();
+ keepField(holder, member.asDexEncodedField());
+ }
+ }
+
+ public void unsafeUnpinField(DexProgramClass holder, DexEncodedField field) {
+ assert holder.type == field.holder();
+ assert !getClassInfo(holder).isPinned();
+ KeepFieldInfo info = this.keepFieldInfo.get(field.toReference());
+ if (info != null && info.isPinned()) {
+ keepFieldInfo.put(field.toReference(), info.builder().unpin().build());
+ }
+ }
+
+ @Override
+ public KeepInfoCollection mutate(Consumer<MutableKeepInfoCollection> mutator) {
+ mutator.accept(this);
+ return this;
+ }
+
+ @Override
+ public void forEachPinnedType(Consumer<DexType> consumer) {
+ keepClassInfo.forEach(
+ (type, info) -> {
+ if (info.isPinned()) {
+ consumer.accept(type);
+ }
+ });
+ }
+
+ @Override
+ public void forEachPinnedMethod(Consumer<DexMethod> consumer) {
+ keepMethodInfo.forEach(
+ (method, info) -> {
+ if (info.isPinned()) {
+ consumer.accept(method);
+ }
+ });
+ }
+
+ @Override
+ public void forEachPinnedField(Consumer<DexField> consumer) {
+ keepFieldInfo.forEach(
+ (field, info) -> {
+ if (info.isPinned()) {
+ consumer.accept(field);
+ }
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
new file mode 100644
index 0000000..98eae1b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -0,0 +1,95 @@
+// 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.shaking;
+
+/** Immutable keep requirements for a method. */
+public final class KeepMethodInfo extends KeepInfo<KeepMethodInfo.Builder, KeepMethodInfo> {
+
+ // Requires all aspects of a method to be kept.
+ private static final KeepMethodInfo TOP = new Builder().makeTop().build();
+
+ // Requires no aspects of a method to be kept.
+ private static final KeepMethodInfo BOTTOM = new Builder().makeBottom().build();
+
+ public static KeepMethodInfo top() {
+ return TOP;
+ }
+
+ public static KeepMethodInfo bottom() {
+ return BOTTOM;
+ }
+
+ private KeepMethodInfo(Builder builder) {
+ super(builder);
+ }
+
+ // This builder is not private as there are known instances where it is safe to modify keep info
+ // in a non-upwards direction.
+ Builder builder() {
+ return new Builder(this);
+ }
+
+ public Joiner joiner() {
+ assert !isTop();
+ return new Joiner(this);
+ }
+
+ @Override
+ public boolean isTop() {
+ return this.equals(top());
+ }
+
+ @Override
+ public boolean isBottom() {
+ return this.equals(bottom());
+ }
+
+ public static class Builder extends KeepInfo.Builder<Builder, KeepMethodInfo> {
+
+ private Builder() {
+ super();
+ }
+
+ private Builder(KeepMethodInfo original) {
+ super(original);
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public KeepMethodInfo getTopInfo() {
+ return TOP;
+ }
+
+ @Override
+ public KeepMethodInfo getBottomInfo() {
+ return BOTTOM;
+ }
+
+ @Override
+ public boolean isEqualTo(KeepMethodInfo other) {
+ return true;
+ }
+
+ @Override
+ public KeepMethodInfo doBuild() {
+ return new KeepMethodInfo(this);
+ }
+ }
+
+ public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepMethodInfo> {
+
+ public Joiner(KeepMethodInfo info) {
+ super(info.builder());
+ }
+
+ @Override
+ Joiner self() {
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
index 0979ef9..c6a1725 100644
--- a/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
+++ b/src/main/java/com/android/tools/r8/shaking/LibraryMethodOverrideAnalysis.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
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.TopDownClassHierarchyTraversal;
@@ -50,8 +49,10 @@
getClassesWithLibraryMethodOverrides(appView);
// Remove all types that are pinned from the initial set of non-escaping classes.
- DexReference.filterDexType(appView.appInfo().pinnedItems.stream())
- .forEach(initialNonEscapingClassesWithLibraryMethodOverrides::remove);
+ appView
+ .appInfo()
+ .getKeepInfo()
+ .forEachPinnedType(initialNonEscapingClassesWithLibraryMethodOverrides::remove);
return initialNonEscapingClassesWithLibraryMethodOverrides;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index ffb940a..9abf328 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.utils.Consumer3;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OriginWithPosition;
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
@@ -55,7 +56,6 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
@@ -71,6 +71,7 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -81,7 +82,7 @@
private final SubtypingInfo subtypingInfo;
private final DirectMappedDexApplication application;
private final Iterable<? extends ProguardConfigurationRule> rules;
- private final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking = new IdentityHashMap<>();
+ private final MutableItemsWithRules noShrinking = new MutableItemsWithRules();
private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet();
private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>();
@@ -98,8 +99,8 @@
private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet();
- private final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>>
- dependentNoShrinking = new IdentityHashMap<>();
+ private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking =
+ new IdentityHashMap<>();
private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
new IdentityHashMap<>();
private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects = new IdentityHashMap<>();
@@ -113,6 +114,9 @@
private final DexStringCache dexStringCache = new DexStringCache();
private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
+ private final Map<OriginWithPosition, List<DexMethod>> assumeNoSideEffectsWarnings =
+ new HashMap<>();
+
public RootSetBuilder(
AppView<? extends AppInfoWithClassHierarchy> appView,
SubtypingInfo subtypingInfo,
@@ -304,6 +308,7 @@
} finally {
application.timing.end();
}
+ generateAssumeNoSideEffectsWarnings();
if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
.visit(appView.appInfo().classes(), this::propagateAssumeRules);
@@ -689,53 +694,62 @@
// TODO(b/67934426): Test this code.
public static void writeSeeds(
AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
- for (DexReference seed : appInfo.getPinnedItems()) {
- if (seed.isDexType()) {
- if (include.test(seed.asDexType())) {
- out.println(seed.toSourceString());
- }
- } else if (seed.isDexField()) {
- DexField field = seed.asDexField();
- if (include.test(field.holder)) {
- out.println(
- field.holder.toSourceString()
- + ": "
- + field.type.toSourceString()
- + " "
- + field.name.toSourceString());
- }
- } else {
- assert seed.isDexMethod();
- DexMethod method = seed.asDexMethod();
- if (!include.test(method.holder)) {
- continue;
- }
- out.print(method.holder.toSourceString() + ": ");
- DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
- if (encodedMethod.accessFlags.isConstructor()) {
- if (encodedMethod.accessFlags.isStatic()) {
- out.print(Constants.CLASS_INITIALIZER_NAME);
- } else {
- String holderName = method.holder.toSourceString();
- String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
- out.print(constrName);
- }
- } else {
- out.print(
- method.proto.returnType.toSourceString() + " " + method.name.toSourceString());
- }
- boolean first = true;
- out.print("(");
- for (DexType param : method.proto.parameters.values) {
- if (!first) {
- out.print(",");
- }
- first = false;
- out.print(param.toSourceString());
- }
- out.println(")");
- }
- }
+ appInfo
+ .getKeepInfo()
+ .forEachPinnedType(
+ type -> {
+ if (include.test(type)) {
+ out.println(type.toSourceString());
+ }
+ });
+ appInfo
+ .getKeepInfo()
+ .forEachPinnedField(
+ field -> {
+ if (include.test(field.holder)) {
+ out.println(
+ field.holder.toSourceString()
+ + ": "
+ + field.type.toSourceString()
+ + " "
+ + field.name.toSourceString());
+ }
+ });
+ appInfo
+ .getKeepInfo()
+ .forEachPinnedMethod(
+ method -> {
+ if (!include.test(method.holder)) {
+ return;
+ }
+ DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method));
+ DexEncodedMethod definition = method.lookupOnClass(holder);
+ if (definition == null) {
+ assert false;
+ return;
+ }
+ out.print(method.holder.toSourceString() + ": ");
+ if (definition.isClassInitializer()) {
+ out.print(Constants.CLASS_INITIALIZER_NAME);
+ } else if (definition.isInstanceInitializer()) {
+ String holderName = method.holder.toSourceString();
+ String constrName = holderName.substring(holderName.lastIndexOf('.') + 1);
+ out.print(constrName);
+ } else {
+ out.print(
+ method.proto.returnType.toSourceString() + " " + method.name.toSourceString());
+ }
+ boolean first = true;
+ out.print("(");
+ for (DexType param : method.proto.parameters.values) {
+ if (!first) {
+ out.print(",");
+ }
+ first = false;
+ out.print(param.toSourceString());
+ }
+ out.println(")");
+ });
out.close();
}
@@ -1002,9 +1016,8 @@
}
// Keep the type if the item is also kept.
dependentNoShrinking
- .computeIfAbsent(item.toReference(), x -> new IdentityHashMap<>())
- .computeIfAbsent(type, k -> new HashSet<>())
- .add(context);
+ .computeIfAbsent(item.toReference(), x -> new MutableItemsWithRules())
+ .addClassWithRule(type, context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
noObfuscation.add(type);
}
@@ -1091,11 +1104,10 @@
if (!modifiers.allowsShrinking) {
if (precondition != null) {
dependentNoShrinking
- .computeIfAbsent(precondition.toReference(), x -> new IdentityHashMap<>())
- .computeIfAbsent(item.toReference(), i -> new HashSet<>())
- .add(keepRule);
+ .computeIfAbsent(precondition.toReference(), x -> new MutableItemsWithRules())
+ .addReferenceWithRule(item.toReference(), keepRule);
} else {
- noShrinking.computeIfAbsent(item.toReference(), i -> new HashSet<>()).add(keepRule);
+ noShrinking.addReferenceWithRule(item.toReference(), keepRule);
}
context.markAsUsed();
}
@@ -1117,6 +1129,7 @@
mayHaveSideEffects.put(item.toReference(), rule);
context.markAsUsed();
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
+ checkAssumeNoSideEffectsWarnings(item, (ProguardAssumeNoSideEffectRule) context, rule);
noSideEffects.put(item.toReference(), rule);
context.markAsUsed();
} else if (context instanceof ProguardWhyAreYouKeepingRule) {
@@ -1263,18 +1276,18 @@
final Set<DexMethod> neverInline;
final Set<DexType> neverClassInline;
- final Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking;
+ final MutableItemsWithRules noShrinking;
final Set<DexReference> noObfuscation;
- final Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking;
+ final Map<DexReference, MutableItemsWithRules> dependentNoShrinking;
final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
final List<DelayedRootSetActionItem> delayedRootSetActionItems;
RootSetBase(
Set<DexMethod> neverInline,
Set<DexType> neverClassInline,
- Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
+ MutableItemsWithRules noShrinking,
Set<DexReference> noObfuscation,
- Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
+ Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
this.neverInline = neverInline;
@@ -1286,10 +1299,6 @@
this.delayedRootSetActionItems = delayedRootSetActionItems;
}
- public boolean noShrinking(DexReference reference) {
- return noShrinking.containsKey(reference);
- }
-
public void forEachClassWithDependentItems(
DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) {
for (DexReference reference : dependentNoShrinking.keySet()) {
@@ -1304,33 +1313,55 @@
}
public void forEachMemberWithDependentItems(
- DexDefinitionSupplier definitions, Consumer<DexEncodedMember<?, ?>> consumer) {
- for (DexReference reference : dependentNoShrinking.keySet()) {
- if (reference.isDexMember()) {
- DexEncodedMember<?, ?> definition = definitions.definitionFor(reference.asDexMember());
- if (definition != null) {
- consumer.accept(definition);
- }
- }
- }
+ DexDefinitionSupplier definitions,
+ BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) {
+ dependentNoShrinking.forEach(
+ (reference, dependentItems) -> {
+ if (reference.isDexMember()) {
+ DexMember<?, ?> member = reference.asDexMember();
+ DexProgramClass holder =
+ asProgramClassOrNull(definitions.definitionForHolder(member));
+ if (holder != null) {
+ DexEncodedMember<?, ?> definition = holder.lookupMember(member);
+ if (definition != null) {
+ consumer.accept(definition, dependentItems);
+ }
+ }
+ }
+ });
}
public void forEachDependentInstanceConstructor(
DexProgramClass clazz,
AppView<?> appView,
- Consumer3<DexProgramClass, ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
+ BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) {
getDependentItems(clazz)
- .forEach(
+ .forEachMethod(
(reference, reasons) -> {
- if (reference.isDexMethod()) {
- DexMethod methodReference = reference.asDexMethod();
- DexProgramClass holder =
- asProgramClassOrNull(appView.definitionForHolder(methodReference));
- if (holder != null) {
- ProgramMethod method = holder.lookupProgramMethod(methodReference);
- if (method != null && method.getDefinition().isInstanceInitializer()) {
- fn.accept(clazz, method, reasons);
- }
+ DexProgramClass holder =
+ asProgramClassOrNull(appView.definitionForHolder(reference));
+ if (holder != null) {
+ ProgramMethod method = holder.lookupProgramMethod(reference);
+ if (method != null && method.getDefinition().isInstanceInitializer()) {
+ fn.accept(method, reasons);
+ }
+ }
+ });
+ }
+
+ public void forEachDependentMember(
+ DexDefinition item,
+ AppView<?> appView,
+ Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
+ getDependentItems(item)
+ .forEachMember(
+ (reference, reasons) -> {
+ DexProgramClass holder =
+ asProgramClassOrNull(appView.definitionForHolder(reference));
+ if (holder != null) {
+ DexEncodedMember<?, ?> member = holder.lookupMember(reference);
+ if (member != null) {
+ fn.accept(item, member, reasons);
}
}
});
@@ -1340,35 +1371,33 @@
DexDefinition item,
AppView<?> appView,
Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
- getDependentItems(item)
- .forEach(
- (reference, reasons) -> {
- DexDefinition definition = appView.definitionFor(reference);
- if (definition != null
- && !definition.isDexClass()
- && !definition.isStaticMember()) {
- fn.accept(item, definition, reasons);
- }
- });
+ forEachDependentMember(
+ item,
+ appView,
+ (precondition, member, reasons) -> {
+ if (!member.isStatic()) {
+ fn.accept(precondition, member, reasons);
+ }
+ });
}
public void forEachDependentStaticMember(
DexDefinition item,
AppView<?> appView,
Consumer3<DexDefinition, DexDefinition, Set<ProguardKeepRuleBase>> fn) {
- getDependentItems(item)
- .forEach(
- (reference, reasons) -> {
- DexDefinition definition = appView.definitionFor(reference);
- if (definition != null && !definition.isDexClass() && definition.isStaticMember()) {
- fn.accept(item, definition, reasons);
- }
- });
+ forEachDependentMember(
+ item,
+ appView,
+ (precondition, member, reasons) -> {
+ if (member.isStatic()) {
+ fn.accept(precondition, member, reasons);
+ }
+ });
}
- Map<DexReference, Set<ProguardKeepRuleBase>> getDependentItems(DexDefinition item) {
- return Collections.unmodifiableMap(
- dependentNoShrinking.getOrDefault(item.toReference(), Collections.emptyMap()));
+ ItemsWithRules getDependentItems(DexDefinition item) {
+ ItemsWithRules found = dependentNoShrinking.get(item.toReference());
+ return found != null ? found : ItemsWithRules.empty();
}
Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) {
@@ -1376,6 +1405,292 @@
}
}
+ abstract static class ItemsWithRules {
+
+ public static ItemsWithRules empty() {
+ return MutableItemsWithRules.EMPTY;
+ }
+
+ public abstract boolean containsClass(DexType type);
+
+ public abstract boolean containsField(DexField field);
+
+ public abstract boolean containsMethod(DexMethod method);
+
+ public final boolean containsReference(DexReference reference) {
+ return reference.apply(this::containsClass, this::containsField, this::containsMethod);
+ }
+
+ public abstract void forEachClass(Consumer<DexType> consumer);
+
+ public abstract void forEachClass(BiConsumer<DexType, Set<ProguardKeepRuleBase>> consumer);
+
+ public abstract void forEachField(Consumer<? super DexField> consumer);
+
+ public abstract void forEachField(
+ BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer);
+
+ public abstract void forEachMember(Consumer<DexMember<?, ?>> consumer);
+
+ public abstract void forEachMember(
+ BiConsumer<DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer);
+
+ public abstract void forEachMethod(Consumer<? super DexMethod> consumer);
+
+ public abstract void forEachMethod(
+ BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer);
+
+ public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type);
+
+ public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field);
+
+ public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method);
+
+ public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) {
+ return reference.apply(
+ this::getRulesForClass, this::getRulesForField, this::getRulesForMethod);
+ }
+ }
+
+ static class MutableItemsWithRules extends ItemsWithRules {
+
+ private static final ItemsWithRules EMPTY =
+ new MutableItemsWithRules(
+ Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap());
+
+ final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules;
+ final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules;
+ final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules;
+
+ MutableItemsWithRules() {
+ this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>());
+ }
+
+ private MutableItemsWithRules(
+ Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules,
+ Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules,
+ Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) {
+ this.classesWithRules = classesWithRules;
+ this.fieldsWithRules = fieldsWithRules;
+ this.methodsWithRules = methodsWithRules;
+ }
+
+ public void addAll(ItemsWithRules items) {
+ items.forEachClass(this::addClassWithRules);
+ items.forEachField(this::addFieldWithRules);
+ items.forEachMethod(this::addMethodWithRules);
+ }
+
+ public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) {
+ classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule);
+ }
+
+ public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
+ classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules);
+ }
+
+ public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) {
+ fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule);
+ }
+
+ public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
+ fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules);
+ }
+
+ public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) {
+ methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule);
+ }
+
+ public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
+ methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules);
+ }
+
+ public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) {
+ reference.accept(
+ this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule);
+ }
+
+ public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+ reference.accept(
+ this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules);
+ }
+
+ @Override
+ public boolean containsClass(DexType type) {
+ return classesWithRules.containsKey(type);
+ }
+
+ @Override
+ public boolean containsField(DexField field) {
+ return fieldsWithRules.containsKey(field);
+ }
+
+ @Override
+ public boolean containsMethod(DexMethod method) {
+ return methodsWithRules.containsKey(method);
+ }
+
+ @Override
+ public void forEachClass(Consumer<DexType> consumer) {
+ classesWithRules.keySet().forEach(consumer);
+ }
+
+ @Override
+ public void forEachClass(BiConsumer<DexType, Set<ProguardKeepRuleBase>> consumer) {
+ classesWithRules.forEach(consumer);
+ }
+
+ @Override
+ public void forEachField(Consumer<? super DexField> consumer) {
+ fieldsWithRules.keySet().forEach(consumer);
+ }
+
+ @Override
+ public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) {
+ fieldsWithRules.forEach(consumer);
+ }
+
+ @Override
+ public void forEachMember(Consumer<DexMember<?, ?>> consumer) {
+ forEachField(consumer);
+ forEachMethod(consumer);
+ }
+
+ @Override
+ public void forEachMember(BiConsumer<DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) {
+ forEachField(consumer);
+ forEachMethod(consumer);
+ }
+
+ @Override
+ public void forEachMethod(Consumer<? super DexMethod> consumer) {
+ methodsWithRules.keySet().forEach(consumer);
+ }
+
+ @Override
+ public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) {
+ methodsWithRules.forEach(consumer);
+ }
+
+ @Override
+ public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) {
+ return classesWithRules.get(type);
+ }
+
+ @Override
+ public Set<ProguardKeepRuleBase> getRulesForField(DexField field) {
+ return fieldsWithRules.get(field);
+ }
+
+ @Override
+ public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) {
+ return methodsWithRules.get(method);
+ }
+
+ public void removeClass(DexType type) {
+ classesWithRules.remove(type);
+ }
+
+ public void removeField(DexField field) {
+ fieldsWithRules.remove(field);
+ }
+
+ public void removeMethod(DexMethod method) {
+ methodsWithRules.remove(method);
+ }
+
+ public void removeReference(DexReference reference) {
+ reference.accept(this::removeClass, this::removeField, this::removeMethod);
+ }
+
+ public void putAll(ItemsWithRules items) {
+ items.forEachClass(this::putClassWithRules);
+ items.forEachField(this::putFieldWithRules);
+ items.forEachMethod(this::putMethodWithRules);
+ }
+
+ public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) {
+ classesWithRules.put(type, rules);
+ }
+
+ public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) {
+ fieldsWithRules.put(field, rules);
+ }
+
+ public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) {
+ methodsWithRules.put(method, rules);
+ }
+
+ public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) {
+ reference.accept(
+ this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules);
+ }
+
+ public int size() {
+ return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size();
+ }
+ }
+
+ private void checkAssumeNoSideEffectsWarnings(
+ DexDefinition item, ProguardAssumeNoSideEffectRule context, ProguardMemberRule rule) {
+ if (rule.getRuleType() == ProguardMemberType.METHOD && rule.isSpecific()) {
+ return;
+ }
+ if (item.isDexEncodedMethod()) {
+ DexEncodedMethod method = item.asDexEncodedMethod();
+ if (method.holder() == options.itemFactory.objectType) {
+ OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition());
+ assumeNoSideEffectsWarnings.computeIfAbsent(key, k -> new ArrayList<>()).add(method.method);
+ }
+ }
+ }
+
+ private boolean isWaitOrNotifyMethod(DexMethod method) {
+ return method.name == options.itemFactory.waitMethodName
+ || method.name == options.itemFactory.notifyMethodName
+ || method.name == options.itemFactory.notifyAllMethodName;
+ }
+
+ private void generateAssumeNoSideEffectsWarnings() {
+ ProguardClassFilter dontWarnPatterns =
+ options.getProguardConfiguration() != null
+ ? options.getProguardConfiguration().getDontWarnPatterns()
+ : ProguardClassFilter.empty();
+ if (dontWarnPatterns.matches(options.itemFactory.objectType)) {
+ return;
+ }
+
+ assumeNoSideEffectsWarnings.forEach(
+ (originWithPosition, methods) -> {
+ boolean waitOrNotifyMethods = methods.stream().anyMatch(this::isWaitOrNotifyMethod);
+ StringBuilder message = new StringBuilder();
+ message.append(
+ "The -assumenosideeffects rule matches methods on `java.lang.Object` with wildcards");
+ if (waitOrNotifyMethods) {
+ message.append(" including the method(s) ");
+ for (int i = 0; i < methods.size(); i++) {
+ if (i > 0) {
+ message.append(i < methods.size() - 1 ? ", " : " and ");
+ }
+ message.append("`");
+ message.append(methods.get(i).toSourceStringWithoutHolder());
+ message.append("`");
+ }
+ message.append(". ");
+ message.append("This will most likely cause problems. ");
+ } else {
+ message.append(". ");
+ message.append("This is most likely not intended. ");
+ }
+ message.append("Consider specifying the methods more precisely.");
+ options.reporter.warning(
+ new StringDiagnostic(
+ message.toString(),
+ originWithPosition.getOrigin(),
+ originWithPosition.getPosition()));
+ });
+ }
+
public static class RootSet extends RootSetBase {
public final ImmutableList<DexReference> reasonAsked;
@@ -1398,7 +1713,7 @@
public final Set<ProguardIfRule> ifRules;
private RootSet(
- Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
+ MutableItemsWithRules noShrinking,
Set<DexReference> noObfuscation,
ImmutableList<DexReference> reasonAsked,
ImmutableList<DexReference> checkDiscarded,
@@ -1418,7 +1733,7 @@
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Map<DexReference, ProguardMemberRule> noSideEffects,
Map<DexReference, ProguardMemberRule> assumedValues,
- Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
+ Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
Set<DexReference> identifierNameStrings,
Set<ProguardIfRule> ifRules,
@@ -1472,8 +1787,7 @@
neverClassInline.addAll(consequentRootSet.neverClassInline);
noObfuscation.addAll(consequentRootSet.noObfuscation);
if (addNoShrinking) {
- consequentRootSet.noShrinking.forEach(
- (type, rules) -> noShrinking.computeIfAbsent(type, k -> new HashSet<>()).addAll(rules));
+ noShrinking.addAll(consequentRootSet.noShrinking);
}
addDependentItems(consequentRootSet.dependentNoShrinking);
consequentRootSet.dependentKeepClassCompatRule.forEach(
@@ -1484,18 +1798,17 @@
}
// Add dependent items that depend on -if rules.
- private void addDependentItems(
- Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentItems) {
+ private void addDependentItems(Map<DexReference, ? extends ItemsWithRules> dependentItems) {
dependentItems.forEach(
(reference, dependence) ->
dependentNoShrinking
- .computeIfAbsent(reference, x -> new IdentityHashMap<>())
+ .computeIfAbsent(reference, x -> new MutableItemsWithRules())
.putAll(dependence));
}
public void copy(DexReference original, DexReference rewritten) {
- if (noShrinking.containsKey(original)) {
- noShrinking.put(rewritten, noShrinking.get(original));
+ if (noShrinking.containsReference(original)) {
+ noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original));
}
if (noObfuscation.contains(original)) {
noObfuscation.add(rewritten);
@@ -1509,7 +1822,7 @@
}
public void prune(DexReference reference) {
- noShrinking.remove(reference);
+ noShrinking.removeReference(reference);
noObfuscation.remove(reference);
noSideEffects.remove(reference);
assumedValues.remove(reference);
@@ -1527,30 +1840,29 @@
Enqueuer enqueuer) {
references.removeIf(
reference -> {
- if (reference.isDexField()) {
- DexEncodedField definition = definitions.definitionFor(reference.asDexField());
- if (definition == null) {
- return true;
- }
- DexClass holder = definitions.definitionFor(definition.holder());
- if (holder.isProgramClass()) {
- return !enqueuer.isFieldReferenced(definition);
- }
- return !enqueuer.isNonProgramTypeLive(holder);
- } else if (reference.isDexMethod()) {
- DexEncodedMethod definition = definitions.definitionFor(reference.asDexMethod());
- if (definition == null) {
- return true;
- }
- DexClass holder = definitions.definitionFor(definition.holder());
- if (holder.isProgramClass()) {
- return !enqueuer.isMethodLive(definition) && !enqueuer.isMethodTargeted(definition);
- }
- return !enqueuer.isNonProgramTypeLive(holder);
- } else {
+ if (reference.isDexType()) {
DexClass definition = definitions.definitionFor(reference.asDexType());
return definition == null || !enqueuer.isTypeLive(definition);
}
+
+ assert reference.isDexMember();
+
+ DexMember<?, ?> member = reference.asDexMember();
+ DexClass holder = definitions.definitionForHolder(member);
+ DexEncodedMember<?, ?> definition = member.lookupOnClass(holder);
+ if (definition == null) {
+ return true;
+ }
+ if (holder.isProgramClass()) {
+ if (definition.isDexEncodedField()) {
+ DexEncodedField field = definition.asDexEncodedField();
+ return !enqueuer.isFieldReferenced(field);
+ }
+ assert definition.isDexEncodedMethod();
+ DexEncodedMethod method = definition.asDexEncodedMethod();
+ return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method);
+ }
+ return !enqueuer.isNonProgramTypeLive(holder);
});
}
@@ -1582,54 +1894,49 @@
}
public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
- for (DexReference reference : noShrinking.keySet()) {
- if (reference.isDexField()) {
- DexField field = reference.asDexField();
- DexEncodedField encodedField = appInfo.definitionFor(field);
- if (encodedField != null
- && (encodedField.isStatic() || isKeptDirectlyOrIndirectly(field.holder, appInfo))) {
- assert appInfo.isFieldRead(encodedField)
- : "Expected kept field `" + field.toSourceString() + "` to be read";
- assert appInfo.isFieldWritten(encodedField)
- : "Expected kept field `" + field.toSourceString() + "` to be written";
- }
- }
- }
+ noShrinking.forEachField(
+ reference -> {
+ DexClass holder = appInfo.definitionForHolder(reference);
+ DexEncodedField field = reference.lookupOnClass(holder);
+ if (field != null
+ && (field.isStatic() || isKeptDirectlyOrIndirectly(field.holder(), appInfo))) {
+ assert appInfo.isFieldRead(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be read";
+ assert appInfo.isFieldWritten(field)
+ : "Expected kept field `" + field.toSourceString() + "` to be written";
+ }
+ });
return true;
}
public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
- for (DexReference reference : noShrinking.keySet()) {
- if (reference.isDexMethod()) {
- DexMethod method = reference.asDexMethod();
- assert appInfo.targetedMethods.contains(method)
- : "Expected kept method `" + method.toSourceString() + "` to be targeted";
- DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
- if (!encodedMethod.accessFlags.isAbstract()
- && isKeptDirectlyOrIndirectly(method.holder, appInfo)) {
- assert appInfo.liveMethods.contains(method)
- : "Expected non-abstract kept method `"
- + method.toSourceString()
- + "` to be live";
- }
- }
- }
+ noShrinking.forEachMethod(
+ reference -> {
+ assert appInfo.targetedMethods.contains(reference)
+ : "Expected kept method `" + reference.toSourceString() + "` to be targeted";
+ DexEncodedMethod method =
+ appInfo.definitionForHolder(reference).lookupMethod(reference);
+ if (!method.isAbstract() && isKeptDirectlyOrIndirectly(method.holder(), appInfo)) {
+ assert appInfo.liveMethods.contains(reference)
+ : "Expected non-abstract kept method `"
+ + reference.toSourceString()
+ + "` to be live";
+ }
+ });
return true;
}
public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
- for (DexReference reference : noShrinking.keySet()) {
- if (reference.isDexType()) {
- DexType type = reference.asDexType();
- assert appInfo.isLiveProgramType(type)
- : "Expected kept type `" + type.toSourceString() + "` to be live";
- }
- }
+ noShrinking.forEachClass(
+ type -> {
+ assert appInfo.isLiveProgramType(type)
+ : "Expected kept type `" + type.toSourceString() + "` to be live";
+ });
return true;
}
private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
- if (noShrinking.containsKey(type)) {
+ if (noShrinking.containsClass(type)) {
return true;
}
DexClass clazz = appInfo.definitionFor(type);
@@ -1643,41 +1950,34 @@
}
public boolean verifyKeptItemsAreKept(DexApplication application, AppInfo appInfo) {
- Set<DexReference> pinnedItems =
- appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null;
-
// Create a mapping from each required type to the set of required members on that type.
- Map<DexType, Set<DexReference>> requiredReferencesPerType = new IdentityHashMap<>();
- for (DexReference reference : noShrinking.keySet()) {
- // Check that `pinnedItems` is a super set of the root set.
- assert pinnedItems == null || pinnedItems.contains(reference)
- : "Expected reference `" + reference.toSourceString() + "` to be pinned";
- if (reference.isDexType()) {
- DexType type = reference.asDexType();
- requiredReferencesPerType.putIfAbsent(type, Sets.newIdentityHashSet());
- } else {
- assert reference.isDexField() || reference.isDexMethod();
- DexType holder =
- reference.isDexField()
- ? reference.asDexField().holder
- : reference.asDexMethod().holder;
- requiredReferencesPerType
- .computeIfAbsent(holder, key -> Sets.newIdentityHashSet())
- .add(reference);
- }
- }
+ Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>();
+ noShrinking.forEachClass(
+ type -> {
+ assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(type)
+ : "Expected reference `" + type.toSourceString() + "` to be pinned";
+ requiredMembersPerType.computeIfAbsent(type, key -> Sets.newIdentityHashSet());
+ });
+ noShrinking.forEachMember(
+ member -> {
+ assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(member)
+ : "Expected reference `" + member.toSourceString() + "` to be pinned";
+ requiredMembersPerType
+ .computeIfAbsent(member.holder, key -> Sets.newIdentityHashSet())
+ .add(member);
+ });
// Run through each class in the program and check that it has members it must have.
for (DexProgramClass clazz : application.classes()) {
- Set<DexReference> requiredReferences =
- requiredReferencesPerType.getOrDefault(clazz.type, ImmutableSet.of());
+ Set<DexMember<?, ?>> requiredMembers =
+ requiredMembersPerType.getOrDefault(clazz.type, ImmutableSet.of());
Set<DexField> fields = null;
Set<DexMethod> methods = null;
- for (DexReference requiredReference : requiredReferences) {
- if (requiredReference.isDexField()) {
- DexField requiredField = requiredReference.asDexField();
+ for (DexMember<?, ?> requiredMember : requiredMembers) {
+ if (requiredMember.isDexField()) {
+ DexField requiredField = requiredMember.asDexField();
if (fields == null) {
// Create a Set of the fields to avoid quadratic behavior.
fields =
@@ -1689,8 +1989,8 @@
: "Expected field `"
+ requiredField.toSourceString()
+ "` from the root set to be present";
- } else if (requiredReference.isDexMethod()) {
- DexMethod requiredMethod = requiredReference.asDexMethod();
+ } else {
+ DexMethod requiredMethod = requiredMember.asDexMethod();
if (methods == null) {
// Create a Set of the methods to avoid quadratic behavior.
methods =
@@ -1702,20 +2002,18 @@
: "Expected method `"
+ requiredMethod.toSourceString()
+ "` from the root set to be present";
- } else {
- assert false;
}
}
- requiredReferencesPerType.remove(clazz.type);
+ requiredMembersPerType.remove(clazz.type);
}
// If the map is non-empty, then a type in the root set was not in the application.
- if (!requiredReferencesPerType.isEmpty()) {
- DexType type = requiredReferencesPerType.keySet().iterator().next();
+ if (!requiredMembersPerType.isEmpty()) {
+ DexType type = requiredMembersPerType.keySet().iterator().next();
DexClass clazz = application.definitionFor(type);
assert clazz == null || clazz.isProgramClass()
: "Unexpected library type in root set: `" + type + "`";
- assert requiredReferencesPerType.isEmpty()
+ assert requiredMembersPerType.isEmpty()
: "Expected type `" + type.toSourceString() + "` to be present";
}
@@ -1726,7 +2024,6 @@
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("RootSet");
-
builder.append("\nnoShrinking: " + noShrinking.size());
builder.append("\nnoObfuscation: " + noObfuscation.size());
builder.append("\nreasonAsked: " + reasonAsked.size());
@@ -1736,13 +2033,6 @@
builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
builder.append("\nifRules: " + ifRules.size());
-
- builder.append("\n\nNo Shrinking:");
- noShrinking.keySet().stream()
- .sorted(Comparator.comparing(DexReference::toSourceString))
- .forEach(a -> builder
- .append("\n").append(a.toSourceString()).append(" ").append(noShrinking.get(a)));
- builder.append("\n");
return builder.toString();
}
}
@@ -1754,9 +2044,9 @@
ConsequentRootSet(
Set<DexMethod> neverInline,
Set<DexType> neverClassInline,
- Map<DexReference, Set<ProguardKeepRuleBase>> noShrinking,
+ MutableItemsWithRules noShrinking,
Set<DexReference> noObfuscation,
- Map<DexReference, Map<DexReference, Set<ProguardKeepRuleBase>>> dependentNoShrinking,
+ Map<DexReference, MutableItemsWithRules> dependentNoShrinking,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
List<DelayedRootSetActionItem> delayedRootSetActionItems) {
super(
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 346caa4..ec2ced1 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -178,7 +178,7 @@
clazz.setStaticFields(reachableStaticFields);
}
clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
- clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
+ clazz.removeEnclosingMethodAttribute(this::isAttributeReferencingPrunedItem);
rewriteNestAttributes(clazz);
usagePrinter.visited();
assert verifyNoDeadFields(clazz);
@@ -300,8 +300,7 @@
// Final classes cannot be abstract, so we have to keep the method in that case.
// Also some other kinds of methods cannot be abstract, so keep them around.
boolean allowAbstract =
- (!options.canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug()
- || clazz.isAbstract())
+ (options.canUseAbstractMethodOnNonAbstractClass() || clazz.isAbstract())
&& !method.isFinal()
&& !method.accessFlags.isNative()
&& !method.accessFlags.isStrict()
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index da9038d..bd3b4f8 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -75,14 +75,12 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -273,12 +271,12 @@
// For all pinned fields, also pin the type of the field (because changing the type of the field
// implicitly changes the signature of the field). Similarly, for all pinned methods, also pin
// the return type and the parameter types of the method.
- extractPinnedItems(appInfo.pinnedItems, AbortReason.PINNED_SOURCE);
-
- // TODO(christofferqa): Remove the invariant that the graph lense should not modify any
- // methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
- extractPinnedItems(appInfo.alwaysInline, AbortReason.ALWAYS_INLINE);
- extractPinnedItems(appInfo.noSideEffects.keySet(), AbortReason.NO_SIDE_EFFECTS);
+ // TODO(b/156715504): Compute referenced-by-pinned in the keep info objects.
+ List<DexReference> pinnedItems = new ArrayList<>();
+ appInfo.getKeepInfo().forEachPinnedType(pinnedItems::add);
+ appInfo.getKeepInfo().forEachPinnedMethod(pinnedItems::add);
+ appInfo.getKeepInfo().forEachPinnedField(pinnedItems::add);
+ extractPinnedItems(pinnedItems, AbortReason.PINNED_SOURCE);
for (DexProgramClass clazz : classes) {
for (DexEncodedMethod method : clazz.methods()) {
@@ -396,7 +394,8 @@
if (result.shouldBreak()) {
return false;
}
- if (sourceClass.getEnclosingMethod() != null || !sourceClass.getInnerClasses().isEmpty()) {
+ if (sourceClass.getEnclosingMethodAttribute() != null
+ || !sourceClass.getInnerClasses().isEmpty()) {
// TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
if (Log.ENABLED) {
AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(sourceClass);
@@ -454,7 +453,8 @@
}
return false;
}
- if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
+ if (targetClass.getEnclosingMethodAttribute() != null
+ || !targetClass.getInnerClasses().isEmpty()) {
// TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
if (Log.ENABLED) {
AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(sourceClass);
@@ -646,14 +646,6 @@
}
private boolean verifyGraphLens(VerticalClassMergerGraphLense graphLense) {
- assert graphLense.assertDefinitionsNotModified(
- appInfo.alwaysInline.stream()
- .map(appInfo::definitionFor)
- .filter(Objects::nonNull)
- .collect(Collectors.toList()));
-
- assert graphLense.assertReferencesNotModified(appInfo.noSideEffects.keySet());
-
// Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
// getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important
// for this check to succeed, since it is not guaranteed that calling lookupMethod() with a
@@ -680,7 +672,7 @@
// that `invoke-super A.method` instructions, which are in one of the methods from C, needs to
// be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually
// pinned, because this rewriting does not affect A.method() in any way.
- assert graphLense.assertReferencesNotModified(appInfo.pinnedItems);
+ assert graphLense.assertPinnedNotModified(appInfo.getKeepInfo());
for (DexProgramClass clazz : appInfo.classes()) {
for (DexEncodedMethod encodedMethod : clazz.methods()) {
@@ -1783,10 +1775,10 @@
private boolean foundIllegalAccess;
private ProgramMethod context;
- private final AppView<?> appView;
+ private final AppView<AppInfoWithLiveness> appView;
private final DexClass source;
- public IllegalAccessDetector(AppView<?> appView, DexClass source) {
+ public IllegalAccessDetector(AppView<AppInfoWithLiveness> appView, DexClass source) {
super(appView.dexItemFactory());
this.appView = appView;
this.source = source;
@@ -1808,7 +1800,7 @@
checkTypeReference(field.holder);
checkTypeReference(field.type);
- DexEncodedField definition = appView.definitionFor(field);
+ DexEncodedField definition = appView.appInfo().resolveField(field).getResolvedField();
if (definition == null || !definition.accessFlags.isPublic()) {
foundIllegalAccess = true;
}
@@ -1817,7 +1809,7 @@
return true;
}
- private boolean checkMethodReference(DexMethod method) {
+ private boolean checkMethodReference(DexMethod method, OptionalBool isInterface) {
if (!foundIllegalAccess) {
DexType baseType =
appView.graphLense().lookupType(method.holder.toBaseType(appView.dexItemFactory()));
@@ -1827,8 +1819,12 @@
for (DexType type : method.proto.parameters.values) {
checkTypeReference(type);
}
- DexEncodedMethod definition = appView.definitionFor(method);
- if (definition == null || !definition.accessFlags.isPublic()) {
+ ResolutionResult resolutionResult =
+ isInterface.isUnknown()
+ ? appView.appInfo().unsafeResolveMethodDueToDexFormat(method)
+ : appView.appInfo().resolveMethod(method, isInterface.isTrue());
+ if (!resolutionResult.isSingleResolution()
+ || !resolutionResult.asSingleResolution().getResolvedMethod().isPublic()) {
foundIllegalAccess = true;
}
}
@@ -1860,7 +1856,7 @@
assert context != null;
GraphLenseLookupResult lookup =
appView.graphLense().lookupMethod(method, context.getReference(), Type.VIRTUAL);
- return checkMethodReference(lookup.getMethod());
+ return checkMethodReference(lookup.getMethod(), OptionalBool.FALSE);
}
@Override
@@ -1868,7 +1864,7 @@
assert context != null;
GraphLenseLookupResult lookup =
appView.graphLense().lookupMethod(method, context.getReference(), Type.DIRECT);
- return checkMethodReference(lookup.getMethod());
+ return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
}
@Override
@@ -1876,7 +1872,7 @@
assert context != null;
GraphLenseLookupResult lookup =
appView.graphLense().lookupMethod(method, context.getReference(), Type.STATIC);
- return checkMethodReference(lookup.getMethod());
+ return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
}
@Override
@@ -1884,7 +1880,7 @@
assert context != null;
GraphLenseLookupResult lookup =
appView.graphLense().lookupMethod(method, context.getReference(), Type.INTERFACE);
- return checkMethodReference(lookup.getMethod());
+ return checkMethodReference(lookup.getMethod(), OptionalBool.TRUE);
}
@Override
@@ -1892,7 +1888,7 @@
assert context != null;
GraphLenseLookupResult lookup =
appView.graphLense().lookupMethod(method, context.getReference(), Type.SUPER);
- return checkMethodReference(lookup.getMethod());
+ return checkMethodReference(lookup.getMethod(), OptionalBool.UNKNOWN);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 1202384..2a0a843 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -17,7 +17,6 @@
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
@@ -55,8 +54,6 @@
private Set<DexMethod> mergedMethods;
private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
- private Map<DexMethod, Set<DexType>> contextsForContextSensitiveMethods;
-
private VerticalClassMergerGraphLense(
AppView<?> appView,
Map<DexType, DexType> typeMap,
@@ -82,24 +79,6 @@
this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges;
}
- public void initializeCacheForLookupMethodInAllContexts() {
- assert contextsForContextSensitiveMethods == null;
- contextsForContextSensitiveMethods = new IdentityHashMap<>();
- contextualVirtualToDirectMethodMaps.forEach(
- (type, virtualToDirectMethodMap) -> {
- for (DexMethod method : virtualToDirectMethodMap.keySet()) {
- contextsForContextSensitiveMethods
- .computeIfAbsent(method, ignore -> Sets.newIdentityHashSet())
- .add(type);
- }
- });
- }
-
- public void unsetCacheForLookupMethodInAllContexts() {
- assert contextsForContextSensitiveMethods != null;
- contextsForContextSensitiveMethods = null;
- }
-
@Override
public DexType getOriginalType(DexType type) {
return previousLense.getOriginalType(type);
@@ -145,22 +124,6 @@
}
@Override
- public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
- assert contextsForContextSensitiveMethods != null;
- ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
- for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
- builder.add(methodMap.getOrDefault(previous, previous));
- Set<DexType> contexts = contextsForContextSensitiveMethods.get(previous);
- if (contexts != null) {
- for (DexType context : contexts) {
- builder.add(contextualVirtualToDirectMethodMaps.get(context).get(previous).getMethod());
- }
- }
- }
- return builder.build();
- }
-
- @Override
public boolean isContextFreeForMethods() {
return contextualVirtualToDirectMethodMaps.isEmpty() && previousLense.isContextFreeForMethods();
}
diff --git a/src/main/java/com/android/tools/r8/utils/AssertionUtils.java b/src/main/java/com/android/tools/r8/utils/AssertionUtils.java
new file mode 100644
index 0000000..c185096
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/AssertionUtils.java
@@ -0,0 +1,13 @@
+// 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.utils;
+
+public class AssertionUtils {
+
+ public static boolean assertNotNull(Object o) {
+ assert o != null;
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 196091e..2d78c0d 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -369,13 +369,6 @@
return 'L' + className.replace(JAVA_PACKAGE_SEPARATOR, INNER_CLASS_SEPARATOR) + ';';
}
- // TODO(b/151195430): Remove once a new version of kotlinx-metadata is released.
- // Kotlin @Metadata deserialization has plain "kotlin", which will be relocated in r8lib.
- // See b/70169921#comment25 for more details.
- private static String backwardRelocatedName(String name) {
- return name.replace("com/android/tools/r8/jetbrains/", "");
- }
-
/**
* Get unqualified class name from its binary name.
*
@@ -512,6 +505,11 @@
return 'L' + descriptor + ';';
}
+ public static boolean isValidBinaryName(String binaryName) {
+ return isValidJavaType(
+ binaryName.replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR));
+ }
+
public static class ModuleAndDescriptor {
private final String module;
private final String descriptor;
diff --git a/src/main/java/com/android/tools/r8/utils/FunctionUtils.java b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
new file mode 100644
index 0000000..64e8537
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FunctionUtils.java
@@ -0,0 +1,18 @@
+// 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.utils;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class FunctionUtils {
+
+ public static <T, R> void forEachApply(
+ Iterable<T> list, Function<T, Consumer<R>> func, R argument) {
+ for (T t : list) {
+ func.apply(t).accept(argument);
+ }
+ }
+}
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 8c92acf..00a2136 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -172,14 +172,12 @@
void disableAllOptimizations() {
disableGlobalOptimizations();
- enableNonNullTracking = false;
enableNameReflectionOptimization = false;
enableStringConcatenationOptimization = false;
}
public void disableGlobalOptimizations() {
enableArgumentRemoval = false;
- enableDynamicTypeOptimization = false;
enableInlining = false;
enableClassInlining = false;
enableClassStaticizer = false;
@@ -215,7 +213,6 @@
public boolean libraryInterfacesMayHaveStaticInitialization = false;
// Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
- public boolean enableDynamicTypeOptimization = true;
public boolean enableFieldAssignmentTracker = true;
public boolean enableFieldBitAccessAnalysis =
System.getProperty("com.android.tools.r8.fieldBitAccessAnalysis") != null;
@@ -224,7 +221,6 @@
public boolean enableArgumentRemoval = true;
public boolean enableUnusedInterfaceRemoval = true;
public boolean enableDevirtualization = true;
- public boolean enableNonNullTracking = true;
public boolean enableInlining =
!Version.isDevelopmentVersion()
|| System.getProperty("com.android.tools.r8.disableinlining") == null;
@@ -1250,6 +1246,22 @@
return minApiLevel >= level.getLevel();
}
+ /**
+ * Dex2Oat issues a warning for abstract methods on non-abstract classes, so we never allow this.
+ *
+ * <p>Note that having an invoke instruction that targets an abstract method on a non-abstract
+ * class will fail with a verification error on Dalvik. Therefore, this must not be more
+ * permissive than {@code return minApiLevel >= AndroidApiLevel.L.getLevel()}.
+ *
+ * <p>See b/132953944.
+ */
+ @SuppressWarnings("ConstantConditions")
+ public boolean canUseAbstractMethodOnNonAbstractClass() {
+ boolean result = false;
+ assert !(result && canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug());
+ return result;
+ }
+
public boolean canUseConstClassInstructions(int cfVersion) {
assert isGeneratingClassFiles();
return cfVersion >= requiredCfVersionForConstClassInstructions();
@@ -1673,7 +1685,7 @@
//
// See b/132953944.
public boolean canHaveDalvikAbstractMethodOnNonAbstractClassVerificationBug() {
- return minApiLevel < AndroidApiLevel.L.getLevel();
+ return isGeneratingDex() && minApiLevel < AndroidApiLevel.L.getLevel();
}
// On dalvik we see issues when using an int value in places where a boolean, byte, char, or short
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index a785262b..e1572f5 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -408,7 +408,14 @@
|| !Objects.equals(mp.caller, lastPosition.caller)) {
break;
}
- lastPosition = mp;
+ // The mapped positions are not guaranteed to be in order, so maintain first and last
+ // position.
+ if (firstPosition.obfuscatedLine > mp.obfuscatedLine) {
+ firstPosition = mp;
+ }
+ if (lastPosition.obfuscatedLine < mp.obfuscatedLine) {
+ lastPosition = mp;
+ }
}
Range obfuscatedRange =
new Range(firstPosition.obfuscatedLine, lastPosition.obfuscatedLine);
diff --git a/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java b/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java
new file mode 100644
index 0000000..8459d19
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OriginWithPosition.java
@@ -0,0 +1,40 @@
+// 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.utils;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.Objects;
+
+public class OriginWithPosition {
+ private final Origin origin;
+ private final Position position;
+
+ public OriginWithPosition(Origin origin, Position position) {
+ this.origin = origin;
+ this.position = position;
+ }
+
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ public Position getPosition() {
+ return position;
+ }
+
+ @Override
+ public int hashCode() {
+ return origin.hashCode() * 13 + position.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof OriginWithPosition) {
+ return Objects.equals(((OriginWithPosition) other).origin, origin)
+ && Objects.equals(((OriginWithPosition) other).position, position);
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index c961003..89880cc 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -24,6 +24,13 @@
return result;
}
+ public static <T> Set<T> newIdentityHashSet(Iterable<T> c1, Iterable<T> c2) {
+ Set<T> result = Sets.newIdentityHashSet();
+ c1.forEach(result::add);
+ c2.forEach(result::add);
+ return result;
+ }
+
public static <T> Set<T> newIdentityHashSet(int capacity) {
return Collections.newSetFromMap(new IdentityHashMap<>(capacity));
}
diff --git a/src/main/java/com/android/tools/r8/utils/TriPredicate.java b/src/main/java/com/android/tools/r8/utils/TriPredicate.java
new file mode 100644
index 0000000..42b76a7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/TriPredicate.java
@@ -0,0 +1,9 @@
+// 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.utils;
+
+public interface TriPredicate<S, T, U> {
+
+ boolean test(S s, T t, U u);
+}
diff --git a/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java b/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java
new file mode 100644
index 0000000..e3707dc
--- /dev/null
+++ b/src/test/examplesProto/proto2/BuilderOnlyReferencedFromDynamicMethodTestClass.java
@@ -0,0 +1,24 @@
+// 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 proto2;
+
+import com.android.tools.r8.proto2.TestProto.Primitives;
+import com.google.protobuf.GeneratedMessageLite;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+public class BuilderOnlyReferencedFromDynamicMethodTestClass {
+
+ public static void main(String[] args) {
+ GeneratedMessageLite<?, ?> primitivesInDisguise;
+ try {
+ primitivesInDisguise = Primitives.parseFrom(new byte[0]);
+ } catch (InvalidProtocolBufferException e) {
+ System.out.println("Unexpected exception: " + e);
+ throw new RuntimeException(e);
+ }
+ Primitives primitives = (Primitives) primitivesInDisguise.toBuilder().build();
+ Printer.print(primitives);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/AssumeNoSideEffects.java b/src/test/java/com/android/tools/r8/AssumeNoSideEffects.java
new file mode 100644
index 0000000..0513e17
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/AssumeNoSideEffects.java
@@ -0,0 +1,10 @@
+// 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+public @interface AssumeNoSideEffects {}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index ce051a5..f395118 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -37,14 +37,20 @@
import java.util.List;
import java.util.Set;
import java.util.zip.ZipFile;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
-public class D8CommandTest {
+@RunWith(Parameterized.class)
+public class D8CommandTest extends TestBase {
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public D8CommandTest(TestParameters parameters) {}
@Test(expected = CompilationFailedException.class)
public void emptyBuilder() throws Throwable {
@@ -267,6 +273,12 @@
parse("--main-dex-list", mainDexList.toString());
}
+ @Test
+ public void testFlagFilePerClass() throws Throwable {
+ D8Command command = parse("--file-per-class");
+ assertTrue(command.getProgramConsumer() instanceof DexFilePerClassFileConsumer);
+ }
+
@Test(expected = CompilationFailedException.class)
public void mainDexListWithFilePerClass() throws Throwable {
Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
@@ -274,6 +286,19 @@
assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
}
+ @Test
+ public void testFlagFilePerClassFile() throws Throwable {
+ D8Command command = parse("--file-per-class-file");
+ assertTrue(command.getProgramConsumer() instanceof DexFilePerClassFileConsumer);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void mainDexListWithFilePerClassFile() throws Throwable {
+ Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
+ D8Command command = parse("--main-dex-list", mainDexList.toString(), "--file-per-class-file");
+ assertTrue(ToolHelper.getApp(command).hasMainDexListResources());
+ }
+
@Test(expected = CompilationFailedException.class)
public void mainDexListWithIntermediate() throws Throwable {
Path mainDexList = temp.newFile("main-dex-list.txt").toPath();
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
index 5f6b792..70518d7 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsMatcher.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import com.android.tools.r8.utils.ExceptionDiagnostic;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -64,7 +65,21 @@
@Override
protected void explain(Description description) {
- description.appendText("orgin ").appendText(origin.toString());
+ description.appendText("origin ").appendText(origin.toString());
+ }
+ };
+ }
+
+ public static Matcher<Diagnostic> diagnosticPosition(Position position) {
+ return new DiagnosticsMatcher() {
+ @Override
+ protected boolean eval(Diagnostic diagnostic) {
+ return diagnostic.getPosition().equals(position);
+ }
+
+ @Override
+ protected void explain(Description description) {
+ description.appendText("position ").appendText(position.getDescription());
}
};
}
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 8bc4820..18571d0 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.debug.CfDebugTestConfig;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ObjectArrays;
@@ -150,4 +151,9 @@
public JvmTestBuilder addVmArguments(String... arguments) {
return addVmArguments(Arrays.asList(arguments));
}
+
+ public JvmTestBuilder addAndroidBuildVersion() {
+ addVmArguments("-D" + AndroidBuildVersion.PROPERTY + "=10000");
+ return addProgramClasses(AndroidBuildVersion.class);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/L8CommandTest.java b/src/test/java/com/android/tools/r8/L8CommandTest.java
index 705ecff..1631830 100644
--- a/src/test/java/com/android/tools/r8/L8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/L8CommandTest.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -15,6 +17,7 @@
import com.android.tools.r8.origin.EmbeddedOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.ThreadUtils;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -24,14 +27,20 @@
import java.util.Collections;
import java.util.List;
import org.junit.Ignore;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
-public class L8CommandTest {
+@RunWith(Parameterized.class)
+public class L8CommandTest extends TestBase {
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public L8CommandTest(TestParameters parameters) {}
@Test(expected = CompilationFailedException.class)
public void emptyBuilder() throws Throwable {
@@ -95,6 +104,34 @@
Marker marker = markers.iterator().next();
}
+ @Test
+ public void testFlagPgConf() throws Exception {
+ TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+ Path pgconf = temp.newFolder().toPath().resolve("pg.conf");
+ FileUtils.writeTextFile(pgconf, "");
+ parse(
+ diagnostics,
+ "--desugared-lib",
+ ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(),
+ "--pg-conf",
+ pgconf.toString());
+ }
+
+ @Test
+ public void testFlagPgConfMissingParameter() {
+ TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+ try {
+ parse(
+ diagnostics,
+ "--desugared-lib",
+ ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(),
+ "--pg-conf");
+ fail("Expected parse error");
+ } catch (CompilationFailedException e) {
+ diagnostics.assertErrorsMatch(diagnosticMessage(containsString("Missing parameter")));
+ }
+ }
+
private L8Command.Builder prepareBuilder(DiagnosticsHandler handler) {
return L8Command.builder(handler)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index b5c518c..149c3c3 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
@@ -37,14 +39,20 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
-public class R8CommandTest {
+@RunWith(Parameterized.class)
+public class R8CommandTest extends TestBase {
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public R8CommandTest(TestParameters parameters) {}
@Test(expected = CompilationFailedException.class)
public void emptyBuilder() throws Throwable {
@@ -759,6 +767,34 @@
}
@Test
+ public void desugaredLibraryWithOutputConf() throws CompilationFailedException {
+ Path pgout = temp.getRoot().toPath().resolve("pgout.conf");
+ R8Command r8Command =
+ parse(
+ "--desugared-lib",
+ "src/library_desugar/desugar_jdk_libs.json",
+ "--desugared-lib-pg-conf-output",
+ pgout.toString());
+ assertFalse(
+ r8Command.getInternalOptions().desugaredLibraryConfiguration.getRewritePrefix().isEmpty());
+ }
+
+ @Test
+ public void desugaredLibraryWithOutputConfMissingArg() {
+ TestDiagnosticMessagesImpl diagnostics = new TestDiagnosticMessagesImpl();
+ try {
+ parse(
+ diagnostics,
+ "--desugared-lib",
+ "src/library_desugar/desugar_jdk_libs.json",
+ "--desugared-lib-pg-conf-output");
+ fail("Expected parse error");
+ } catch (CompilationFailedException e) {
+ diagnostics.assertErrorsMatch(diagnosticMessage(containsString("Missing parameter")));
+ }
+ }
+
+ @Test
public void numThreadsOption() throws Exception {
assertEquals(ThreadUtils.NOT_SPECIFIED, parse().getThreadCount());
assertEquals(1, parse("--thread-count", "1").getThreadCount());
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 6326718..eddf1ee 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -513,9 +513,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non-R8/D8 running.
"156-register-dex-file-multi-loader",
"412-new-array",
@@ -525,9 +522,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non-R8/D8 running.
"004-checker-UnsafeTest18",
"005-annotations",
@@ -544,9 +538,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non R8/D8 running.
"004-checker-UnsafeTest18",
"004-NativeAllocations",
@@ -567,9 +558,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non R8/D8 running.
"004-checker-UnsafeTest18",
"004-NativeAllocations",
@@ -590,9 +578,6 @@
// Addition of checks for super-class-initialization cause this to abort on non-ToT art.
"008-exceptions",
- // Fails due to non-matching Exception messages.
- "201-built-in-except-detail-messages",
-
// Generally fails on non R8/D8 running.
"004-checker-UnsafeTest18",
"004-NativeAllocations",
@@ -821,6 +806,24 @@
.put("138-duplicate-classes-check", TestCondition.any())
// Array index out of bounds exception.
.put("150-loadlibrary", TestCondition.any())
+ // Fails due to non-matching Exception messages.
+ .put(
+ "201-built-in-except-detail-messages",
+ TestCondition.or(
+ TestCondition.match(
+ TestCondition.compilers(
+ CompilerUnderTest.D8, CompilerUnderTest.D8_AFTER_R8CF),
+ TestCondition.runtimes(
+ DexVm.Version.V4_0_4,
+ DexVm.Version.V4_4_4,
+ DexVm.Version.V5_1_1,
+ DexVm.Version.V6_0_1,
+ DexVm.Version.V7_0_0)),
+ TestCondition.match(
+ TestCondition.compilers(
+ CompilerUnderTest.R8,
+ CompilerUnderTest.R8CF,
+ CompilerUnderTest.R8_AFTER_D8))))
// Uses dex file version 37 and therefore only runs on Android N and above.
.put(
"370-dex-v37",
@@ -1170,13 +1173,6 @@
"435-new-instance"
);
- private static List<String> requireUninstantiatedTypeOptimizationToBeDisabled = ImmutableList.of(
- // This test inspects the message of the exception that is thrown when calling a virtual
- // method with a null-receiver. This message changes when the invocation is rewritten to
- // "throw null".
- "201-built-in-except-detail-messages"
- );
-
private static List<String> hasMissingClasses = ImmutableList.of(
"091-override-package-private-method",
"003-omnibus-opcodes",
@@ -1279,8 +1275,6 @@
private final boolean disableInlining;
// Whether to disable class inlining
private final boolean disableClassInlining;
- // Whether to disable the uninitialized type optimization.
- private final boolean disableUninstantiatedTypeOptimization;
// Has missing classes.
private final boolean hasMissingClasses;
// Explicitly disable desugaring.
@@ -1304,7 +1298,6 @@
boolean outputMayDiffer,
boolean disableInlining,
boolean disableClassInlining,
- boolean disableUninstantiatedTypeOptimization,
boolean hasMissingClasses,
boolean disableDesugaring,
List<String> keepRules,
@@ -1323,7 +1316,6 @@
this.outputMayDiffer = outputMayDiffer;
this.disableInlining = disableInlining;
this.disableClassInlining = disableClassInlining;
- this.disableUninstantiatedTypeOptimization = disableUninstantiatedTypeOptimization;
this.hasMissingClasses = hasMissingClasses;
this.disableDesugaring = disableDesugaring;
this.keepRules = keepRules;
@@ -1354,7 +1346,6 @@
disableInlining,
true, // Disable class inlining for JCTF tests.
false,
- false,
true, // Disable desugaring for JCTF tests.
ImmutableList.of(),
null);
@@ -1383,7 +1374,6 @@
disableInlining,
true, // Disable class inlining for JCTF tests.
false,
- false,
true, // Disable desugaring for JCTF tests.
ImmutableList.of(),
null);
@@ -1548,7 +1538,6 @@
outputMayDiffer.contains(name),
requireInliningToBeDisabled.contains(name),
requireClassInliningToBeDisabled.contains(name),
- requireUninstantiatedTypeOptimizationToBeDisabled.contains(name),
hasMissingClasses.contains(name),
false,
keepRules.getOrDefault(name, ImmutableList.of()),
@@ -1619,7 +1608,6 @@
private final boolean disableInlining;
private final boolean disableClassInlining;
- private final boolean disableUninstantiatedTypeOptimization;
private final boolean hasMissingClasses;
private final boolean disableDesugaring;
private final List<String> keepRules;
@@ -1628,7 +1616,6 @@
private CompilationOptions(TestSpecification spec) {
this.disableInlining = spec.disableInlining;
this.disableClassInlining = spec.disableClassInlining;
- this.disableUninstantiatedTypeOptimization = spec.disableUninstantiatedTypeOptimization;
this.hasMissingClasses = spec.hasMissingClasses;
this.disableDesugaring = spec.disableDesugaring;
this.keepRules = spec.keepRules;
@@ -1643,9 +1630,6 @@
if (disableClassInlining) {
options.enableClassInlining = false;
}
- if (disableUninstantiatedTypeOptimization) {
- options.enableUninstantiatedTypeOptimization = false;
- }
// Some tests actually rely on missing classes for what they test.
options.ignoreMissingClasses = hasMissingClasses;
if (configuration != null) {
@@ -1664,7 +1648,6 @@
CompilationOptions options = (CompilationOptions) o;
return disableInlining == options.disableInlining
&& disableClassInlining == options.disableClassInlining
- && disableUninstantiatedTypeOptimization == options.disableUninstantiatedTypeOptimization
&& hasMissingClasses == options.hasMissingClasses;
}
@@ -1673,7 +1656,6 @@
return Objects.hash(
disableInlining,
disableClassInlining,
- disableUninstantiatedTypeOptimization,
hasMissingClasses);
}
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index a109e20..9d0684d 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -48,6 +48,7 @@
private AllowedDiagnosticMessages allowedDiagnosticMessages = AllowedDiagnosticMessages.NONE;
private boolean allowUnusedProguardConfigurationRules = false;
+ private boolean enableAssumeNoSideEffectsAnnotations = false;
private boolean enableConstantArgumentAnnotations = false;
private boolean enableInliningAnnotations = false;
private boolean enableMemberValuePropagationAnnotations = false;
@@ -135,7 +136,8 @@
box.proguardConfiguration,
box.syntheticProguardRules,
proguardMapBuilder.toString(),
- graphConsumer);
+ graphConsumer,
+ builder.getMinApiLevel());
switch (allowedDiagnosticMessages) {
case ALL:
compileResult.assertDiagnosticThatMatches(new IsAnything<>());
@@ -349,6 +351,21 @@
return self();
}
+ public T enableAssumeNoSideEffectsAnnotations() {
+ return enableAssumeNoSideEffectsAnnotations(AssumeNoSideEffects.class.getPackage().getName());
+ }
+
+ public T enableAssumeNoSideEffectsAnnotations(String annotationPackageName) {
+ if (!enableAssumeNoSideEffectsAnnotations) {
+ enableAssumeNoSideEffectsAnnotations = true;
+ addInternalKeepRules(
+ "-assumenosideeffects class * { @"
+ + annotationPackageName
+ + ".AssumeNoSideEffects <methods>; }");
+ }
+ return self();
+ }
+
public T enableInliningAnnotations() {
return enableInliningAnnotations(NeverInline.class.getPackage().getName());
}
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 3a16807..5dac954 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -14,7 +14,6 @@
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
-import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> {
@@ -23,6 +22,7 @@
private final List<ProguardConfigurationRule> syntheticProguardRules;
private final String proguardMap;
private final CollectingGraphConsumer graphConsumer;
+ private final int minApiLevel;
R8TestCompileResult(
TestState state,
@@ -31,12 +31,14 @@
ProguardConfiguration proguardConfiguration,
List<ProguardConfigurationRule> syntheticProguardRules,
String proguardMap,
- CollectingGraphConsumer graphConsumer) {
+ CollectingGraphConsumer graphConsumer,
+ int minApiLevel) {
super(state, app, outputMode);
this.proguardConfiguration = proguardConfiguration;
this.syntheticProguardRules = syntheticProguardRules;
this.proguardMap = proguardMap;
this.graphConsumer = graphConsumer;
+ this.minApiLevel = minApiLevel;
}
@Override
@@ -65,11 +67,11 @@
}
@Override
- public CodeInspector inspector() throws IOException, ExecutionException {
+ public CodeInspector inspector() throws IOException {
return new CodeInspector(app, proguardMap);
}
- public GraphInspector graphInspector() throws IOException, ExecutionException {
+ public GraphInspector graphInspector() throws IOException {
assert graphConsumer != null;
return new GraphInspector(graphConsumer, inspector());
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 39356e1..0816496 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -243,6 +243,11 @@
return self();
}
+ public CR setSystemProperty(String name, String value) {
+ vmArguments.add("-D" + name + "=" + value);
+ return self();
+ }
+
public Path writeToZip() throws IOException {
Path file = state.getNewTempFolder().resolve("out.zip");
writeToZip(file);
@@ -429,6 +434,13 @@
withArt6Plus64BitsLib && vm.getVersion().isAtLeast(DexVm.Version.V6_0_1)
? builder -> builder.appendArtOption("--64")
: builder -> {};
+ commandConsumer =
+ commandConsumer.andThen(
+ builder -> {
+ for (String vmArgument : vmArguments) {
+ builder.appendArtOption(vmArgument);
+ }
+ });
ProcessResult result =
ToolHelper.runArtRaw(
classPath, mainClass, commandConsumer, vm, withArtFrameworks, arguments);
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index b7869e4..490cf54 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -62,6 +63,14 @@
private PrintStream oldStderr = null;
protected OutputMode outputMode = OutputMode.DexIndexed;
+ private boolean isAndroidBuildVersionAdded = false;
+
+ public T addAndroidBuildVersion() {
+ addProgramClasses(AndroidBuildVersion.class);
+ isAndroidBuildVersionAdded = true;
+ return self();
+ }
+
TestCompilerBuilder(TestState state, B builder, Backend backend) {
super(state, builder);
this.backend = backend;
@@ -133,6 +142,9 @@
cr =
internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build))
.addRunClasspathFiles(additionalRunClassPath);
+ if (isAndroidBuildVersionAdded) {
+ cr.setSystemProperty(AndroidBuildVersion.PROPERTY, "" + builder.getMinApiLevel());
+ }
return cr;
} finally {
if (stdout != null) {
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
index ac3ef82..ed0e8f1 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessages.java
@@ -36,7 +36,7 @@
// Match exact.
- default TestDiagnosticMessages assertDiagnosticsMatch(Matcher matcher) {
+ default TestDiagnosticMessages assertDiagnosticsMatch(Matcher<Diagnostic> matcher) {
return assertDiagnosticsMatch(Collections.singletonList(matcher));
}
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 19a700d..54ad442 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -201,7 +201,8 @@
if (!matchedDiagnostics.contains(diagnostic)) {
builder
.append("\n - ")
- .append(diagnostics.getClass().getName())
+ .append(diagnostic.getClass().getName())
+ .append(": ")
.append(diagnostic.getDiagnosticMessage());
}
}
@@ -220,7 +221,8 @@
for (Diagnostic diagnostic : diagnostics) {
builder
.append("\n - ")
- .append(diagnostics.getClass().getName())
+ .append(diagnostic.getClass().getName())
+ .append(": ")
.append(diagnostic.getDiagnosticMessage());
}
builder.append("\nAll matchers:");
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index bf3bb9d..16fd725 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -113,6 +113,10 @@
return self();
}
+ public RR assertSuccessWithEmptyOutput() {
+ return assertSuccessWithOutput("");
+ }
+
public RR assertSuccessWithOutputLines(String... expected) {
return assertSuccessWithOutputLines(Arrays.asList(expected));
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
index 4c1caf7..9a29671 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/WrapperMergeTest.java
@@ -5,66 +5,155 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.TestRuntime.DexRuntime;
-import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
import com.android.tools.r8.ir.desugar.DesugaredLibraryWrapperSynthesizer;
+import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import java.nio.file.Path;
import java.util.Arrays;
+import java.util.Set;
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 WrapperMergeTest extends DesugaredLibraryTestBase {
+ private static final String EXPECTED = StringUtils.lines("[1, 2, 3]", "[2, 3, 4]");
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public WrapperMergeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addAndroidBuildVersion()
+ .addProgramClassesAndInnerClasses(MyArrays1.class)
+ .addProgramClassesAndInnerClasses(MyArrays2.class)
+ .addProgramClasses(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
@Test
public void testWrapperMerge() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
// Multiple wrapper classes have to be merged here.
- Path path1 = testForD8()
- .addProgramClasses(Executor1.class)
- .setMinApi(AndroidApiLevel.B)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
- .compile()
- .inspect(this::assertWrappers)
- .writeToZip();
- Path path2 = testForD8()
- .addProgramClasses(Executor2.class)
- .setMinApi(AndroidApiLevel.B)
- .enableCoreLibraryDesugaring(AndroidApiLevel.B)
- .compile()
- .inspect(this::assertWrappers)
- .writeToZip();
+ Path path1 = compileWithCoreLibraryDesugaring(MyArrays1.class);
+ Path path2 = compileWithCoreLibraryDesugaring(MyArrays2.class);
testForD8()
.addProgramFiles(path1, path2)
+ .addProgramClasses(TestClass.class)
+ .addAndroidBuildVersion()
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .setMinApi(parameters.getApiLevel())
.compile()
- .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, AndroidApiLevel.B)
- .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor1.class)
- .assertSuccessWithOutput(StringUtils.lines("[1, 2, 3]"));
+ .inspect(this::assertWrappers)
+ .inspect(this::assertNoDuplicates)
+ .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private Path compileWithCoreLibraryDesugaring(Class<?> clazz) throws Exception {
+ return testForD8()
+ .addProgramClassesAndInnerClasses(clazz)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .inspect(this::assertWrappers)
+ .writeToZip();
+ }
+
+ private void assertNoDuplicates(CodeInspector inspector) {
+ Object2ReferenceMap<String, Set<FoundClassSubject>> map = new Object2ReferenceOpenHashMap<>();
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ map.computeIfAbsent(clazz.getFinalName(), k -> Sets.newIdentityHashSet()).add(clazz);
+ }
+ for (Set<FoundClassSubject> duplicates : map.values()) {
+ if (duplicates.size() > 1) {
+ fail("Unexpected duplicates: " + duplicates);
+ }
+ }
+ }
+
+ private boolean hasNativeIntUnaryOperator() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
}
private void assertWrappers(CodeInspector inspector) {
- assertEquals(2,inspector.allClasses().stream().filter(c -> c.getOriginalName().contains(
- DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX)).count());
+ assertEquals(
+ hasNativeIntUnaryOperator() ? 0 : 2,
+ inspector.allClasses().stream()
+ .filter(
+ c ->
+ c.getOriginalName().contains(DesugaredLibraryWrapperSynthesizer.WRAPPER_PREFIX))
+ .count());
}
- static class Executor1 {
+ static class MyArrays1 {
- public static void main(String[] args) {
- int[] ints = new int[3];
- Arrays.setAll(ints,x->x+1);
- System.out.println(Arrays.toString(ints));
+ interface IntGenerator {
+ int generate(int index);
+ }
+
+ public static void setAll(int[] ints, IntGenerator generator) {
+ if (AndroidBuildVersion.VERSION >= 24) {
+ java.util.Arrays.setAll(ints, generator::generate);
+ } else {
+ for (int i = 0; i < ints.length; i++) {
+ ints[i] = generator.generate(i);
+ }
+ }
}
}
- static class Executor2 {
+ static class MyArrays2 {
- public static void main(String[] args) {
- int[] ints = new int[3];
- Arrays.setAll(ints,x->x+2);
- System.out.println(Arrays.toString(ints));
+ interface IntGenerator {
+ int generate(int index);
+ }
+
+ public static void setAll(int[] ints, IntGenerator generator) {
+ if (AndroidBuildVersion.VERSION >= 24) {
+ java.util.Arrays.setAll(ints, generator::generate);
+ } else {
+ for (int i = 0; i < ints.length; i++) {
+ ints[i] = generator.generate(i);
+ }
+ }
}
}
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ int[] ints = new int[3];
+ MyArrays1.setAll(ints, x -> x + 1);
+ System.out.println(Arrays.toString(ints));
+ MyArrays2.setAll(ints, x -> x + 2);
+ System.out.println(Arrays.toString(ints));
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
index 03e7f70..3b9b321 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/Java11D8CompilationTest.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -20,15 +19,11 @@
@RunWith(Parameterized.class)
public class Java11D8CompilationTest extends TestBase {
- public Java11D8CompilationTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- private final TestParameters parameters;
+ public Java11D8CompilationTest(TestParameters parameters) {}
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimesStartingFromIncluding(Version.V5_1_1).build();
+ return getTestParameters().withNoneRuntime().build();
}
private static void assertNoNests(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
index 2cfe861..f828413 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingEnumUnboxingTest.java
@@ -4,21 +4,13 @@
package com.android.tools.r8.enumunboxing;
-import static junit.framework.TestCase.assertTrue;
-import static org.junit.Assert.assertEquals;
-
import com.android.tools.r8.NeverClassInline;
-import com.android.tools.r8.NeverInline;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInstanceFieldMain.EnumInstanceField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumInterfaceMain.EnumInterface;
import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticFieldMain.EnumStaticField;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumStaticMethodMain.EnumStaticMethod;
-import com.android.tools.r8.enumunboxing.FailingEnumUnboxingTest.EnumVirtualMethodMain.EnumVirtualMethod;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -29,11 +21,8 @@
public class FailingEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?>[] FAILURES = {
- EnumInterface.class,
EnumStaticField.class,
EnumInstanceField.class,
- EnumStaticMethod.class,
- EnumVirtualMethod.class
};
private final TestParameters parameters;
@@ -61,15 +50,12 @@
}
R8TestCompileResult compile =
r8FullTestBuilder
- .noTreeShaking() // Disabled to avoid merging Itf into EnumInterface.
- .enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.addKeepRules(enumKeepRules.getKeepRule())
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.allowDiagnosticInfoMessages()
.setMinApi(parameters.getApiLevel())
- .compile()
- .inspect(this::assertEnumsAsExpected);
+ .compile();
for (Class<?> failure : FAILURES) {
R8TestRunResult run =
compile
@@ -81,44 +67,6 @@
}
}
- private void assertEnumsAsExpected(CodeInspector inspector) {
- assertEquals(1, inspector.clazz(EnumInterface.class).getDexProgramClass().interfaces.size());
-
- assertTrue(inspector.clazz(EnumStaticField.class).uniqueFieldWithName("X").isPresent());
- assertTrue(inspector.clazz(EnumInstanceField.class).uniqueFieldWithName("a").isPresent());
-
- assertEquals(
- 5,
- inspector
- .clazz(EnumStaticMethod.class)
- .getDexProgramClass()
- .getMethodCollection()
- .numberOfDirectMethods());
- assertEquals(1, inspector.clazz(EnumVirtualMethod.class).virtualMethods().size());
- }
-
- static class EnumInterfaceMain {
-
- public static void main(String[] args) {
- System.out.println(EnumInterface.A.ordinal());
- System.out.println(0);
- }
-
- @NeverClassInline
- enum EnumInterface implements Itf {
- A,
- B,
- C
- }
-
- interface Itf {
-
- default int ordinal() {
- return -1;
- }
- }
- }
-
static class EnumStaticFieldMain {
public static void main(String[] args) {
@@ -158,53 +106,4 @@
System.out.println(10);
}
}
-
- static class EnumStaticMethodMain {
-
- @NeverClassInline
- enum EnumStaticMethod {
- A,
- B,
- C;
-
- // Enum cannot be unboxed if it has a static method, we do not inline so the method is
- // present.
- @NeverInline
- static int foo() {
- return Math.addExact(-1, 0);
- }
- }
-
- public static void main(String[] args) {
- System.out.println(EnumStaticMethod.A.ordinal());
- System.out.println(0);
- System.out.println(EnumStaticMethod.foo());
- System.out.println(-1);
- }
- }
-
- static class EnumVirtualMethodMain {
-
- public static void main(String[] args) {
- EnumVirtualMethod e1 = EnumVirtualMethod.A;
- System.out.println(e1.ordinal());
- System.out.println(0);
- System.out.println(e1.valueOf());
- System.out.println(-1);
- }
-
- @NeverClassInline
- enum EnumVirtualMethod {
- A,
- B,
- C;
-
- // Enum cannot be unboxed if it has a virtual method, we do not inline so the method is
- // present.
- @NeverInline
- int valueOf() {
- return Math.addExact(-1, 0);
- }
- }
- }
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
new file mode 100644
index 0000000..5dcf75d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/InterfaceEnumUnboxingTest.java
@@ -0,0 +1,270 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+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 InterfaceEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private static final Class<?>[] FAILURES = {
+ FailureDefaultMethodUsed.class, FailureUsedAsInterface.class,
+ };
+
+ private static final Class<?>[] SUCCESSES = {
+ SuccessAbstractMethod.class,
+ SuccessEmptyInterface.class,
+ SuccessUnusedDefaultMethod.class,
+ SuccessUnusedDefaultMethodOverride.class,
+ SuccessUnusedDefaultMethodOverrideEnum.class
+ };
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final KeepRule enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public InterfaceEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxingFailure() throws Exception {
+ R8TestCompileResult compile =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InterfaceEnumUnboxingTest.class)
+ .addKeepMainRules(SUCCESSES)
+ .addKeepMainRules(FAILURES)
+ .noMinification()
+ .enableMergeAnnotations()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ for (Class<?> failure : FAILURES) {
+ testClass(compile, failure, true);
+ }
+ for (Class<?> success : SUCCESSES) {
+ testClass(compile, success, false);
+ }
+ }
+
+ private void testClass(R8TestCompileResult compile, Class<?> testClass, boolean failure)
+ throws Exception {
+ R8TestRunResult run =
+ compile
+ .inspectDiagnosticMessages(
+ m -> {
+ for (Class<?> declaredClass : testClass.getDeclaredClasses()) {
+ if (declaredClass.isEnum()) {
+ if (failure) {
+ assertEnumIsBoxed(declaredClass, testClass.getSimpleName(), m);
+ } else {
+ assertEnumIsUnboxed(declaredClass, testClass.getSimpleName(), m);
+ }
+ }
+ }
+ })
+ .run(parameters.getRuntime(), testClass)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ static class SuccessEmptyInterface {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.ordinal());
+ System.out.println(0);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ @NeverMerge
+ interface Itf {}
+ }
+
+ static class SuccessUnusedDefaultMethodOverrideEnum {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.ordinal());
+ System.out.println(0);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int ordinal() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+
+ static class SuccessUnusedDefaultMethodOverride {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.method());
+ System.out.println(5);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C;
+
+ @Override
+ @NeverInline
+ public int method() {
+ return System.currentTimeMillis() > 0 ? 5 : -5;
+ }
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int method() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+
+ static class SuccessUnusedDefaultMethod {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.ordinal());
+ System.out.println(0);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int method() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+
+ static class SuccessAbstractMethod {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.method());
+ System.out.println(5);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C;
+
+ @Override
+ @NeverInline
+ public int method() {
+ return System.currentTimeMillis() > 0 ? 5 : -5;
+ }
+ }
+
+ @NeverMerge
+ interface Itf {
+ int method();
+ }
+ }
+
+ static class FailureDefaultMethodUsed {
+
+ public static void main(String[] args) {
+ System.out.println(EnumInterface.A.method());
+ System.out.println(3);
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int method() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+
+ static class FailureUsedAsInterface {
+
+ public static void main(String[] args) {
+ print(EnumInterface.A);
+ System.out.println(5);
+ }
+
+ @NeverInline
+ public static void print(Itf itf) {
+ System.out.println(itf.method());
+ }
+
+ @NeverClassInline
+ enum EnumInterface implements Itf {
+ A,
+ B,
+ C;
+
+ @Override
+ @NeverInline
+ public int method() {
+ return System.currentTimeMillis() > 0 ? 5 : -5;
+ }
+ }
+
+ @NeverMerge
+ interface Itf {
+ @NeverInline
+ default int method() {
+ return System.currentTimeMillis() > 0 ? 3 : -3;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
similarity index 88%
rename from src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
rename to src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
index b388ed7..8bd41c3 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
@@ -14,7 +14,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class OrdinalEnumUnboxingTest extends EnumUnboxingTestBase {
+public class OrdinalHashCodeEnumUnboxingTest extends EnumUnboxingTestBase {
private static final Class<?> ENUM_CLASS = MyEnum.class;
@@ -27,7 +27,7 @@
return enumUnboxingTestParameters();
}
- public OrdinalEnumUnboxingTest(
+ public OrdinalHashCodeEnumUnboxingTest(
TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
this.parameters = parameters;
this.enumValueOptimization = enumValueOptimization;
@@ -36,7 +36,7 @@
@Test
public void testEnumUnboxing() throws Exception {
- Class<Ordinal> classToTest = Ordinal.class;
+ Class<?> classToTest = OrdinalHashCode.class;
R8TestRunResult run =
testForR8(parameters.getBackend())
.addProgramClasses(classToTest, ENUM_CLASS)
@@ -61,11 +61,13 @@
C
}
- static class Ordinal {
+ static class OrdinalHashCode {
public static void main(String[] args) {
System.out.println(MyEnum.A.ordinal());
System.out.println(0);
+ System.out.println(MyEnum.A.hashCode());
+ System.out.println(0);
}
}
}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
index d8d781e..b4f97ab 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ToStringEnumUnboxingTest.java
@@ -65,9 +65,13 @@
@SuppressWarnings("ConstantConditions")
public static void main(String[] args) {
System.out.println(MyEnum.A.toString());
+ System.out.println("A");
System.out.println(MyEnum.A.name());
+ System.out.println("A");
System.out.println(MyEnum.B.toString());
+ System.out.println("B");
System.out.println(MyEnum.B.name());
+ System.out.println("B");
try {
System.out.println(((MyEnum) null).toString());
} catch (NullPointerException e) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
new file mode 100644
index 0000000..6496c7a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/VirtualMethodsEnumUnboxingTest.java
@@ -0,0 +1,166 @@
+// 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.enumunboxing;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import java.util.List;
+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 VirtualMethodsEnumUnboxingTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final KeepRule enumKeepRules;
+
+ @Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public VirtualMethodsEnumUnboxingTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = VirtualMethods.class;
+ R8TestRunResult run =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(VirtualMethodsEnumUnboxingTest.class)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> {
+ assertEnumIsUnboxed(MyEnum.class, classToTest.getSimpleName(), m);
+ assertEnumIsUnboxed(MyEnum2.class, classToTest.getSimpleName(), m);
+ })
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccess();
+ assertLines2By2Correct(run.getStdOut());
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ public void print(Object o) {
+ System.out.println(o);
+ }
+
+ @NeverInline
+ public void printEnum(MyEnum e) {
+ System.out.println(e.ordinal());
+ }
+
+ @NeverInline
+ public MyEnum returnEnum(boolean bool) {
+ return bool ? MyEnum.A : MyEnum.B;
+ }
+
+ @NeverInline
+ protected void printProtected() {
+ System.out.println("protected");
+ }
+
+ @NeverInline
+ void printPackagePrivate() {
+ System.out.println("package-private");
+ }
+
+ @NeverInline
+ private void printPrivate() {
+ System.out.println("private");
+ }
+
+ @NeverInline
+ public void callPrivate() {
+ System.out.print("call: ");
+ printPrivate();
+ }
+ }
+
+ // Use two enums to test collision.
+ enum MyEnum2 {
+ A,
+ B,
+ C;
+
+ @NeverInline
+ public void print(Object o) {
+ System.out.println("2" + o);
+ }
+
+ @NeverInline
+ public void printEnum(MyEnum e) {
+ System.out.println("2" + e.ordinal());
+ }
+
+ @NeverInline
+ public MyEnum returnEnum(boolean bool) {
+ return bool ? MyEnum.B : MyEnum.C;
+ }
+ }
+
+ static class VirtualMethods {
+
+ public static void main(String[] args) {
+ testCustomMethods();
+ testCustomMethods2();
+ testNonPublicMethods();
+ }
+
+ @NeverInline
+ private static void testNonPublicMethods() {
+ MyEnum.A.printPrivate();
+ System.out.println("private");
+ MyEnum.A.printPackagePrivate();
+ System.out.println("package-private");
+ MyEnum.A.printProtected();
+ System.out.println("protected");
+ MyEnum.A.callPrivate();
+ System.out.println("call: private");
+ }
+
+ @NeverInline
+ private static void testCustomMethods() {
+ MyEnum.A.print("print");
+ System.out.println("print");
+ MyEnum.A.printEnum(MyEnum.A);
+ System.out.println(0);
+ System.out.println((MyEnum.A.returnEnum(true).ordinal()));
+ System.out.println(0);
+ }
+
+ @NeverInline
+ private static void testCustomMethods2() {
+ MyEnum2.A.print("print");
+ System.out.println("2print");
+ MyEnum2.A.printEnum(MyEnum.A);
+ System.out.println(20);
+ System.out.println((MyEnum2.A.returnEnum(true).ordinal()));
+ System.out.println(1);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
index 183ee3e..118bcce 100644
--- a/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/Gmail18082615TreeShakeJarVerificationTest.java
@@ -51,7 +51,9 @@
.assertAllInfoMessagesMatch(
anyOf(
equalTo("Ignoring option: -optimizations"),
- containsString("Proguard configuration rule does not match anything")))
+ containsString("Proguard configuration rule does not match anything"),
+ containsString("Invalid parameter counts in MethodParameter attributes"),
+ containsString("Methods with invalid MethodParameter attributes")))
.assertAllWarningMessagesMatch(containsString("Ignoring option:"));
int appSize = compileResult.app.applicationSize();
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
new file mode 100644
index 0000000..6405047
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderOnlyReferencedFromDynamicMethodTest.java
@@ -0,0 +1,78 @@
+// 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.internal.proto;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+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 com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Proto2BuilderOnlyReferencedFromDynamicMethodTest extends ProtoShrinkingTestBase {
+
+ private static final String MAIN = "proto2.BuilderOnlyReferencedFromDynamicMethodTestClass";
+
+ private static List<Path> PROGRAM_FILES =
+ ImmutableList.of(PROTO2_EXAMPLES_JAR, PROTO2_PROTO_JAR, PROTOBUF_LITE_JAR);
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public Proto2BuilderOnlyReferencedFromDynamicMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(PROGRAM_FILES)
+ .addKeepMainRule(MAIN)
+ .addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
+ .allowAccessModification()
+ .allowDiagnosticMessages()
+ .allowUnusedProguardConfigurationRules()
+ .enableProtoShrinking()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .assertAllInfoMessagesMatch(
+ containsString("Proguard configuration rule does not match anything"))
+ .assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), MAIN)
+ .assertSuccessWithOutputLines(
+ "false", "0", "false", "", "false", "0", "false", "0", "false", "");
+ }
+
+ private void inspect(CodeInspector outputInspector) {
+ verifyBuilderIsAbsent(outputInspector);
+ }
+
+ private void verifyBuilderIsAbsent(CodeInspector outputInspector) {
+ ClassSubject generatedMessageLiteBuilder =
+ outputInspector.clazz("com.google.protobuf.GeneratedMessageLite$Builder");
+ assertThat(generatedMessageLiteBuilder, isPresent());
+ assertFalse(generatedMessageLiteBuilder.isAbstract());
+ assertThat(
+ outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
+ not(isPresent()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 8c98b46..dc2b296 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -171,9 +171,7 @@
assertThat(
outputInspector.clazz(
"com.android.tools.r8.proto2.Shrinking$HasFlaggedOffExtension$Builder"),
- mains.equals(ImmutableList.of("proto2.HasFlaggedOffExtensionBuilderTestClass"))
- ? isPresent()
- : not(isPresent()));
+ not(isPresent()));
assertThat(
outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/DeadConstructorWithCycleTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/DeadConstructorWithCycleTest.java
new file mode 100644
index 0000000..e64fedd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/DeadConstructorWithCycleTest.java
@@ -0,0 +1,69 @@
+// 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.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 com.android.tools.r8.NeverClassInline;
+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 DeadConstructorWithCycleTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DeadConstructorWithCycleTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DeadConstructorWithCycleTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> assertThat(inspector.clazz(A.class), not(isPresent())))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Object o1 = null;
+ Object o2 = null;
+ for (int i = 1; i <= 2; i++) {
+ o1 = new A(o1, o2);
+ o2 = new A(o1, o2);
+ }
+ }
+ }
+
+ @NeverClassInline
+ static class A {
+
+ Object f1;
+ Object f2;
+
+ A(Object o1, Object o2) {
+ this.f1 = o1;
+ this.f2 = o2;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
index a46524a..3ce1ee1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/AlwaysThrowNullTest.java
@@ -166,7 +166,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
private final TestParameters parameters;
@@ -228,7 +228,7 @@
testForD8()
.release()
.addProgramClassesAndInnerClasses(MAIN)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, false);
@@ -244,10 +244,9 @@
.enableMemberValuePropagationAnnotations()
.addKeepMainRule(MAIN)
.noMinification()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
test(result, true);
}
-
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index dac8b92..a9f77ce 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -62,7 +62,7 @@
while (it.hasNext()) {
prev = curr != null && !curr.isGoto() ? curr : prev;
curr = it.next();
- if (curr.isAssumeNonNull()) {
+ if (curr.isAssumeWithNonNullAssumption()) {
// Make sure non-null is added to the right place.
assertTrue(prev == null
|| prev.throwsOnNullInput()
@@ -160,11 +160,11 @@
if (count == 0) {
// First one in the very first line: its value should not be replaced by NonNullMarker
// because this instruction will happen _before_ non-null.
- assertFalse(iput.value().definition.isAssumeNonNull());
+ assertFalse(iput.value().definition.isAssumeWithNonNullAssumption());
} else if (count == 1) {
// Second one after a safe invocation, which should use the value added by
// NonNullMarker.
- assertTrue(iput.object().definition.isAssumeNonNull());
+ assertTrue(iput.object().definition.isAssumeWithNonNullAssumption());
}
count++;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
index a1c69fa..83c5243 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ObjectsRequireNonNullTest.java
@@ -141,7 +141,9 @@
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 0, 0);
+ // TODO(b/157427150): would be able to remove the call to requireNonNull() if we knew that it
+ // throws an NullPointerException that does not have a message.
+ test(result, 0, 1);
}
static class ObjectsRequireNonNullTestMain {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ThrowNPEWithMessageIfParameterIsNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ThrowNPEWithMessageIfParameterIsNullTest.java
new file mode 100644
index 0000000..924278e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ThrowNPEWithMessageIfParameterIsNullTest.java
@@ -0,0 +1,58 @@
+// 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.ir.optimize;
+
+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 ThrowNPEWithMessageIfParameterIsNullTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ThrowNPEWithMessageIfParameterIsNullTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ThrowNPEWithMessageIfParameterIsNullTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ try {
+ checkNotNull(null);
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+ @NeverInline
+ static void checkNotNull(Object o) {
+ if (o == null) {
+ throw new NullPointerException("Hello world!");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
index 4cab645..52736da 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/dynamictype/DynamicTypeOptimizationTest.java
@@ -7,13 +7,11 @@
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
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 com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -26,17 +24,14 @@
@RunWith(Parameterized.class)
public class DynamicTypeOptimizationTest extends TestBase {
- private final boolean enableDynamicTypeOptimization;
private final TestParameters parameters;
- @Parameterized.Parameters(name = "{1}, enable dynamic type optimization: {0}")
+ @Parameterized.Parameters(name = "{0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
}
- public DynamicTypeOptimizationTest(
- boolean enableDynamicTypeOptimization, TestParameters parameters) {
- this.enableDynamicTypeOptimization = enableDynamicTypeOptimization;
+ public DynamicTypeOptimizationTest(TestParameters parameters) {
this.parameters = parameters;
}
@@ -47,10 +42,8 @@
.addKeepMainRule(TestClass.class)
// Keep B to ensure that we will treat it as being instantiated.
.addKeepClassRulesWithAllowObfuscation(B.class)
- .addOptionsModification(
- options -> options.enableDynamicTypeOptimization = enableDynamicTypeOptimization)
.enableInliningAnnotations()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::inspect)
.run(parameters.getRuntime(), TestClass.class)
@@ -81,8 +74,7 @@
MethodSubject testInstanceOfRemovalMethod =
mainClassSubject.uniqueMethodWithName("testInstanceOfRemoval");
assertThat(testInstanceOfRemovalMethod, isPresent());
- assertEquals(
- enableDynamicTypeOptimization,
+ assertTrue(
testInstanceOfRemovalMethod
.streamInstructions()
.noneMatch(instruction -> instruction.isInstanceOf(aClassSubject.getFinalName())));
@@ -92,27 +84,16 @@
MethodSubject testMethodInliningMethod =
mainClassSubject.uniqueMethodWithName("testMethodInlining");
assertThat(testMethodInliningMethod, isPresent());
- assertEquals(
- enableDynamicTypeOptimization, interfaceSubject.uniqueMethodWithName("world").isAbsent());
- if (!enableDynamicTypeOptimization) {
- assertThat(
- testMethodInliningMethod, invokesMethod(interfaceSubject.uniqueMethodWithName("world")));
- }
+ assertTrue(interfaceSubject.uniqueMethodWithName("world").isAbsent());
// Verify that exclamationMark() has been rebound in testMethodRebinding() unless the dynamic
// type optimization is disabled.
MethodSubject testMethodRebindingMethod =
mainClassSubject.uniqueMethodWithName("testMethodRebinding");
assertThat(testMethodRebindingMethod, isPresent());
- if (enableDynamicTypeOptimization) {
- assertThat(
- testMethodRebindingMethod,
- invokesMethod(aClassSubject.uniqueMethodWithName("exclamationMark")));
- } else {
- assertThat(
- testMethodRebindingMethod,
- invokesMethod(interfaceSubject.uniqueMethodWithName("exclamationMark")));
- }
+ assertThat(
+ testMethodRebindingMethod,
+ invokesMethod(aClassSubject.uniqueMethodWithName("exclamationMark")));
}
static class TestClass {
@@ -170,6 +151,7 @@
static class A implements I {
+ @NeverInline
@Override
public void hello() {
System.out.print("Hello");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
index 1cbeaae..791bbae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -111,16 +110,14 @@
}
}
- @Parameters(name = "{1}, enable dynamic type optimization: {0}")
+ @Parameters(name = "{0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
}
- private final boolean enableDynamicTypeOptimization;
private final TestParameters parameters;
- public InstanceOfRemovalTest(boolean enableDynamicTypeOptimization, TestParameters parameters) {
- this.enableDynamicTypeOptimization = enableDynamicTypeOptimization;
+ public InstanceOfRemovalTest(TestParameters parameters) {
this.parameters = parameters;
}
@@ -161,10 +158,8 @@
testForR8(parameters.getBackend())
.addProgramClasses(A.class, B.class, TestClass.class)
.addKeepMainRule(TestClass.class)
- .addOptionsModification(
- options -> options.enableDynamicTypeOptimization = enableDynamicTypeOptimization)
.enableInliningAnnotations()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expected)
.inspector();
@@ -182,7 +177,6 @@
MethodSubject barMethodSubject = testClass.uniqueMethodWithName("bar");
Iterator<InstructionSubject> barInstructionIterator =
barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
- assertEquals(
- enableDynamicTypeOptimization ? 4 : 6, Streams.stream(barInstructionIterator).count());
+ assertEquals(4, Streams.stream(barInstructionIterator).count());
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
index 289e095..3940548 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -46,7 +47,7 @@
.addProgramClasses(I.class, J.class, Main.class)
.addProgramClassFileData(getAimplementsI())
.addKeepMainRule(Main.class)
- .addKeepRules("-keep class **A { createA(); }")
+ .enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.addOptionsModification(
options -> options.enableUninstantiatedTypeOptimizationForInterfaces = true)
@@ -62,7 +63,7 @@
.addProgramClasses(I.class, J.class, Main.class)
.addProgramClassFileData(getAimplementsI())
.addKeepMainRule(Main.class)
- .addKeepRules("-keep class **A { createA(); }")
+ .enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.addOptionsModification(
options -> options.enableUninstantiatedTypeOptimizationForInterfaces = false)
@@ -76,10 +77,13 @@
public interface J extends I {}
public static class A implements J {
+
+ @NeverInline
public static J createA() {
return new A();
}
+ @NeverInline
public void f() {
System.out.println("In A.f()");
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
index 854ac14..828f4c0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InvokeMethodWithNonNullParamCheckTest.java
@@ -7,7 +7,6 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
@@ -107,15 +106,16 @@
.anyMatch(InstructionSubject::isThrow));
if (shouldHaveThrow) {
- // Check that there are no invoke instructions targeting the methods on `Static` and
- // `Virtual`.
+ // TODO(b/157427150): Check that there are no invoke instructions targeting the methods on
+ // `Static` and `Virtual`. This requires that we know that their methods throw
+ // NullPointerExceptions without messages.
Streams.stream(methodSubject.iterateInstructions())
.filter(InstructionSubject::isInvoke)
.forEach(
ins -> {
ClassSubject clazz = inspector.clazz(ins.getMethod().holder.toSourceString());
- assertNotEquals(clazz.getOriginalName(), Static.class.getTypeName());
- assertNotEquals(clazz.getOriginalName(), Virtual.class.getTypeName());
+ // assertNotEquals(clazz.getOriginalName(), Static.class.getTypeName());
+ // assertNotEquals(clazz.getOriginalName(), Virtual.class.getTypeName());
});
}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 8ea5334..b4d2c3a 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -132,7 +132,8 @@
}
@Override
- public BasicBlock split(IRCode code, ListIterator<BasicBlock> blockIterator) {
+ public BasicBlock split(
+ IRCode code, ListIterator<BasicBlock> blockIterator, boolean keepCatchHandlers) {
throw new Unimplemented();
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
index 904f69f..6df1462 100644
--- a/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
+++ b/src/test/java/com/android/tools/r8/kotlin/coroutines/KotlinxCoroutinesTestRunner.java
@@ -7,12 +7,13 @@
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.kotlin.metadata.KotlinMetadataTestBase;
import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import java.io.IOException;
@@ -27,7 +28,7 @@
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class KotlinxCoroutinesTestRunner extends KotlinTestBase {
+public class KotlinxCoroutinesTestRunner extends KotlinMetadataTestBase {
private static final String PKG = "kotlinx-coroutines-1.3.6";
private static final Path BASE_LIBRARY =
@@ -63,22 +64,44 @@
@Test
public void runKotlinxCoroutinesTests_smoke() throws Exception {
- Path baseJar =
- kotlinc(KOTLINC, targetVersion)
- .addArguments(
- "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
- "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi",
- "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi")
- .addClasspathFiles(DEPENDENCIES)
- .addClasspathFiles(BASE_LIBRARY)
- .addSourceFiles(TEST_SOURCES)
- .compile();
- runTestsInJar(baseJar, BASE_LIBRARY);
+ runTestsInJar(compileTestSources(BASE_LIBRARY), BASE_LIBRARY);
}
- private void runTestsInJar(Path testJar, Path deps) throws Exception {
+ @Test
+ public void runKotlinxCoroutinesTests_r8() throws Exception {
+ Path baseJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(BASE_LIBRARY)
+ .addKeepAllClassesRule()
+ .addKeepAllAttributes()
+ // The BASE_LIBRARY contains proguard rules that do not match.
+ .allowUnusedProguardConfigurationRules()
+ .addKeepRules(
+ "-dontwarn reactor.blockhound.integration.BlockHoundIntegration",
+ "-dontwarn org.junit.runners.model.Statement",
+ "-dontwarn org.junit.rules.TestRule")
+ .compile()
+ .inspect(inspector -> assertEqualMetadata(new CodeInspector(BASE_LIBRARY), inspector))
+ .writeToZip();
+ Path testJar = compileTestSources(baseJar);
+ runTestsInJar(testJar, baseJar);
+ }
+
+ private Path compileTestSources(Path baseJar) throws Exception {
+ return kotlinc(KOTLINC, targetVersion)
+ .addArguments(
+ "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
+ "-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi",
+ "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi")
+ .addClasspathFiles(DEPENDENCIES)
+ .addClasspathFiles(baseJar)
+ .addSourceFiles(TEST_SOURCES)
+ .compile();
+ }
+
+ private void runTestsInJar(Path testJar, Path baseJar) throws Exception {
List<Path> dependencies = new ArrayList<>(DEPENDENCIES);
- dependencies.add(deps);
+ dependencies.add(baseJar);
dependencies.add(testJar);
ZipUtils.iter(
testJar.toString(),
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
index 87b5d10..99aaf1b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/KotlinMetadataTestBase.java
@@ -3,13 +3,25 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.kotlin.metadata;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertNotNull;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNull;
+
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.kotlin.AbstractR8KotlinTestBase;
+import com.android.tools.r8.kotlin.KotlinMetadataWriter;
import com.android.tools.r8.utils.DescriptorUtils;
+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 junit.framework.TestCase;
+import kotlinx.metadata.jvm.KotlinClassHeader;
+import kotlinx.metadata.jvm.KotlinClassMetadata;
-abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase {
+public abstract class KotlinMetadataTestBase extends AbstractR8KotlinTestBase {
- KotlinMetadataTestBase(KotlinTargetVersion targetVersion) {
+ public KotlinMetadataTestBase(KotlinTargetVersion targetVersion) {
super(targetVersion);
}
@@ -27,4 +39,29 @@
static final String KT_FUNCTION1 = "Lkotlin/Function1;";
static final String KT_COMPARABLE = "Lkotlin/Comparable;";
+
+ public void assertEqualMetadata(CodeInspector originalInspector, CodeInspector rewrittenInspector)
+ throws Exception {
+ for (FoundClassSubject clazzSubject : originalInspector.allClasses()) {
+ ClassSubject r8Clazz = rewrittenInspector.clazz(clazzSubject.getOriginalName());
+ assertThat(r8Clazz, isPresent());
+ KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
+ KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
+ if (originalMetadata == null) {
+ assertNull(rewrittenMetadata);
+ continue;
+ }
+ assertNotNull(rewrittenMetadata);
+ KotlinClassHeader originalHeader = originalMetadata.getHeader();
+ KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
+ TestCase.assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
+ // TODO(b/154199572): Should we check for meta-data version?
+ TestCase.assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
+ // We cannot assert equality of the data since it may be ordered differently. Instead we use
+ // the KotlinMetadataWriter.
+ String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
+ String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
+ TestCase.assertEquals(expected, actual);
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
new file mode 100644
index 0000000..c6dd4fd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -0,0 +1,96 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteCrossinlineAnonFunctionTest extends KotlinMetadataTestBase {
+
+ private final String EXPECTED = StringUtils.lines("foo");
+ private static final String PKG_LIB = PKG + ".crossinline_anon_lib";
+ private static final String PKG_APP = PKG + ".crossinline_anon_app";
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteCrossinlineAnonFunctionTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private final TestParameters parameters;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataForLib() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(libJars.get(targetVersion))
+ // Allow renaming A to ensure that we rename in the flexible upper bound type.
+ .addKeepAllClassesRule()
+ .addKeepAllAttributes()
+ .compile()
+ .writeToZip();
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addProgramFiles(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
new file mode 100644
index 0000000..ce26b77
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineConcreteFunctionTest.java
@@ -0,0 +1,96 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteCrossinlineConcreteFunctionTest extends KotlinMetadataTestBase {
+
+ private final String EXPECTED = StringUtils.lines("foo");
+ private static final String PKG_LIB = PKG + ".crossinline_concrete_lib";
+ private static final String PKG_APP = PKG + ".crossinline_concrete_app";
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteCrossinlineConcreteFunctionTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private final TestParameters parameters;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataForLib() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(libJars.get(targetVersion))
+ .addKeepAllClassesRule()
+ .addKeepAllAttributes()
+ .compile()
+ .writeToZip();
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
new file mode 100644
index 0000000..50007fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -0,0 +1,174 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteDelegatedPropertyTest extends KotlinMetadataTestBase {
+
+ private static final String PKG_LIB = PKG + ".delegated_property_lib";
+ private static final String PKG_APP = PKG + ".delegated_property_app";
+ private static final String EXPECTED_MAIN =
+ StringUtils.lines(
+ "foo has been assigned to 'customDelegate' in"
+ + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
+ "foo has been read in CustomDelegate from 'customDelegate' in"
+ + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
+ "foo",
+ "read-only has been read in CustomReadOnlyDelegate from 'customReadOnlyDelegate' in"
+ + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
+ "read-only",
+ "Generating lazy string",
+ "42",
+ "Hello World!",
+ "Hello World!",
+ "Jane Doe",
+ "42",
+ "Checking property for image",
+ "Checking property for text",
+ "image_id",
+ "text_id");
+ private static final String EXPECTED_REFLECT =
+ StringUtils.lines(
+ "foo has been assigned to 'customDelegate' in"
+ + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
+ "foo");
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteDelegatedPropertyTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private final TestParameters parameters;
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED_MAIN);
+ }
+
+ @Test
+ public void smokeTestReflect() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(
+ DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".Main_reflectKt")
+ .assertSuccessWithOutput(EXPECTED_REFLECT);
+ }
+
+ @Test
+ public void testMetadataForLib() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(libJars.get(targetVersion))
+ .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }")
+ .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }")
+ .addKeepRules("-keep class " + PKG_LIB + ".User { *; }")
+ .addKeepRules("-keep class " + PKG_LIB + ".ProvidedDelegates { *; }")
+ .compile()
+ // TODO(b/157988734): When we start modeling localDelegatedProperties, inspect the code.
+ .writeToZip();
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED_MAIN);
+ }
+
+ @Test
+ public void testMetadataForReflect() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
+ .addProgramFiles(libJars.get(targetVersion))
+ .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }")
+ .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }")
+ .addKeepRules("-keep class " + PKG_LIB + ".CustomDelegate { *; }")
+ .compile()
+ .writeToZip();
+ ProcessResult result =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(
+ DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compileRaw();
+ assertEquals(1, result.exitCode);
+ assertThat(
+ result.stderr,
+ containsString(
+ "unsupported [reference to the synthetic extension property for a Java get/set"
+ + " method]"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
index a17f7dc..71d888c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePassThroughTest.java
@@ -4,25 +4,12 @@
package com.android.tools.r8.kotlin.metadata;
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static junit.framework.TestCase.assertEquals;
-import static junit.framework.TestCase.assertNotNull;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNull;
-
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.kotlin.KotlinMetadataWriter;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
-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 java.io.IOException;
import java.util.Collection;
-import java.util.concurrent.ExecutionException;
-import kotlinx.metadata.jvm.KotlinClassHeader;
-import kotlinx.metadata.jvm.KotlinClassMetadata;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -53,31 +40,8 @@
.addKeepKotlinMetadata()
.addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.compile()
- .inspect(this::inspect);
- }
-
- public void inspect(CodeInspector inspector) throws IOException, ExecutionException {
- CodeInspector stdLibInspector = new CodeInspector(ToolHelper.getKotlinStdlibJar());
- for (FoundClassSubject clazzSubject : stdLibInspector.allClasses()) {
- ClassSubject r8Clazz = inspector.clazz(clazzSubject.getOriginalName());
- assertThat(r8Clazz, isPresent());
- KotlinClassMetadata originalMetadata = clazzSubject.getKotlinClassMetadata();
- KotlinClassMetadata rewrittenMetadata = r8Clazz.getKotlinClassMetadata();
- if (originalMetadata == null) {
- assertNull(rewrittenMetadata);
- continue;
- }
- assertNotNull(rewrittenMetadata);
- KotlinClassHeader originalHeader = originalMetadata.getHeader();
- KotlinClassHeader rewrittenHeader = rewrittenMetadata.getHeader();
- assertEquals(originalHeader.getKind(), rewrittenHeader.getKind());
- // TODO(b/154199572): Should we check for meta-data version?
- assertEquals(originalHeader.getPackageName(), rewrittenHeader.getPackageName());
- // We cannot assert equality of the data since it may be ordered differently. Instead we use
- // the KotlinMetadataWriter.
- String expected = KotlinMetadataWriter.kotlinMetadataToString("", originalMetadata);
- String actual = KotlinMetadataWriter.kotlinMetadataToString("", rewrittenMetadata);
- assertEquals(expected, actual);
- }
+ .inspect(
+ inspector ->
+ assertEqualMetadata(new CodeInspector(ToolHelper.getKotlinStdlibJar()), inspector));
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_app/main.kt
new file mode 100644
index 0000000..99a3882
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_app/main.kt
@@ -0,0 +1,18 @@
+// 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.kotlin.metadata.crossinline_anon_app
+
+import com.android.tools.r8.kotlin.metadata.crossinline_anon_lib.Context
+import com.android.tools.r8.kotlin.metadata.crossinline_anon_lib.Handler
+
+fun main() {
+ Handler({ context, throwable ->
+ println(context)
+ }).handle(object : Context {
+ override fun toString(): String {
+ return "foo"
+ }
+ }, NullPointerException())
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_lib/lib.kt
new file mode 100644
index 0000000..b820f4a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_anon_lib/lib.kt
@@ -0,0 +1,19 @@
+// 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.kotlin.metadata.crossinline_anon_lib
+
+public interface Context {
+
+}
+
+public inline fun Handler(crossinline handler: (Context, Throwable) -> Unit): Handler =
+ object : Handler {
+ override fun handle(context: Context, exception: Throwable) =
+ handler.invoke(context, exception)
+ }
+
+public interface Handler {
+ fun handle(context: Context, exception: Throwable)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_app/main.kt
new file mode 100644
index 0000000..4c5c3a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_app/main.kt
@@ -0,0 +1,18 @@
+// 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.kotlin.metadata.crossinline_concrete_app
+
+import com.android.tools.r8.kotlin.metadata.crossinline_concrete_lib.Context
+import com.android.tools.r8.kotlin.metadata.crossinline_concrete_lib.Handler
+
+fun main() {
+ Handler({ context, throwable ->
+ println(context)
+ }).handle(object : Context {
+ override fun toString(): String {
+ return "foo"
+ }
+ }, NullPointerException())
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_lib/lib.kt
new file mode 100644
index 0000000..cf5da42
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/crossinline_concrete_lib/lib.kt
@@ -0,0 +1,25 @@
+// 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.kotlin.metadata.crossinline_concrete_lib
+
+public interface Context {
+
+}
+
+public inline fun Handler(crossinline handler: (Context, Throwable) -> Unit): Handler =
+ ConcreteClass().getHandler(handler)
+
+class ConcreteClass {
+
+ inline fun getHandler(crossinline handler: (Context, Throwable) -> Unit): Handler =
+ object : Handler {
+ override fun handle(context: Context, exception: Throwable) =
+ handler.invoke(context, exception)
+ }
+}
+
+public interface Handler {
+ fun handle(context: Context, exception: Throwable)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
new file mode 100644
index 0000000..7bfd637
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
@@ -0,0 +1,31 @@
+// 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.kotlin.metadata.delegated_property_app
+
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.ProvidedDelegates
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.User
+
+fun main() {
+
+ val delegates = Delegates()
+ delegates.customDelegate = Resource("foo");
+ println(delegates.customDelegate)
+ println(delegates.customReadOnlyDelegate)
+ println(delegates.lazyString)
+ println(delegates.localDelegatedProperties { Resource("Hello World!") })
+
+ val user = User(mapOf(
+ "name" to "Jane Doe",
+ "age" to 42
+ ))
+
+ println(user.name)
+ println(user.age)
+
+ val providedDelegates = ProvidedDelegates()
+ println(providedDelegates.image)
+ println(providedDelegates.text)
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt
new file mode 100644
index 0000000..0369a86
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt
@@ -0,0 +1,21 @@
+// 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.kotlin.metadata.delegated_property_app
+
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.CustomDelegate
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates
+import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource
+import kotlin.reflect.KMutableProperty0
+import kotlin.reflect.jvm.isAccessible
+
+fun main() {
+ val delegates = Delegates()
+ delegates.customDelegate = Resource("foo");
+ println(delegates::customDelegate.getResource())
+}
+
+inline fun KMutableProperty0<*>.getResource(): Resource {
+ isAccessible = true
+ return (getDelegate() as CustomDelegate).resource
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt
new file mode 100644
index 0000000..54c58df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt
@@ -0,0 +1,88 @@
+// 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.kotlin.metadata.delegated_property_lib
+
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+class Resource(private var s : String = "") {
+
+ override fun toString(): String {
+ return s;
+ }
+}
+
+class CustomDelegate(var resource: Resource = Resource()) {
+
+ operator fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
+ println("$resource has been read in CustomDelegate from '" +
+ "${property.name}' in ${thisRef?.javaClass?.typeName}")
+ return resource;
+ }
+
+ operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
+ println("$value has been assigned to '${property.name}'" +
+ " in ${thisRef?.javaClass?.typeName}")
+ this.resource = value
+ }
+}
+
+class CustomReadOnlyDelegate(private var resource : Resource = Resource("read-only"))
+ : ReadOnlyProperty<Any?, Resource> {
+ override fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
+ println("$resource has been read in CustomReadOnlyDelegate" +
+ " from '${property.name}' in ${thisRef?.javaClass?.typeName}")
+ return resource;
+ }
+}
+
+class Delegates {
+
+ var customDelegate : Resource by CustomDelegate()
+ val customReadOnlyDelegate : Resource by CustomReadOnlyDelegate()
+ val lazyString : String by lazy {
+ println("Generating lazy string")
+ "42"
+ }
+
+ fun localDelegatedProperties(compute: () -> Resource) : Resource {
+ val foo by lazy(compute)
+ println(foo)
+ return foo
+ }
+}
+class User(val map : Map<String, Any?>) {
+ val name : String by map
+ val age: Int by map
+}
+
+class ResourceDelegate(val r : Resource): ReadOnlyProperty<ProvidedDelegates, Resource> {
+ override fun getValue(thisRef: ProvidedDelegates, property: KProperty<*>): Resource {
+ return r
+ }
+}
+
+class ResourceLoader(val id: Resource) {
+ operator fun provideDelegate(
+ thisRef: ProvidedDelegates,
+ prop: KProperty<*>
+ ): ReadOnlyProperty<ProvidedDelegates, Resource> {
+ checkProperty(prop.name)
+ return ResourceDelegate(id)
+ }
+
+ private fun checkProperty(name: String) {
+ println("Checking property for " + name)
+ }
+}
+
+class ProvidedDelegates {
+ fun bindResource(id: String): ResourceLoader {
+ return ResourceLoader(Resource(id))
+ }
+
+ val image by bindResource("image_id")
+ val text by bindResource("text_id")
+}
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 cacf66b..26186aa 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -635,9 +635,10 @@
.forEach(
p -> {
try {
- CodeInspector i = new CodeInspector(AndroidApp.builder().addProgramFiles(p).build());
+ CodeInspector i =
+ new CodeInspector(AndroidApp.builder().addProgramFiles(p).build());
assertFalse("Found " + clazz + " in file " + p, i.clazz(clazz).isPresent());
- } catch (IOException | ExecutionException e) {
+ } catch (IOException e) {
e.printStackTrace();
}
});
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
index 48fc320..5562395 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
@@ -13,8 +13,10 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import org.junit.Test;
@@ -77,6 +79,18 @@
assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
});
}
+
+ @Override
+ public void configure(R8TestBuilder<?> builder) {
+ builder.applyIf(mode == CompilationMode.RELEASE, R8TestBuilder::enableForceInliningAnnotations);
+ }
+
+ @Override
+ public void inspect(CodeInspector inspector) {
+ if (mode == CompilationMode.RELEASE) {
+ assertEquals(compat ? 2 : 1, inspector.clazz(Main.class).allMethods().size());
+ }
+ }
}
class Main {
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java b/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java
new file mode 100644
index 0000000..02ed76d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/LineNumberRangeTest.java
@@ -0,0 +1,284 @@
+// 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.naming.retrace;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+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.references.Reference;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.Matchers;
+import com.android.tools.r8.utils.codeinspector.Matchers.LinePosition;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class LineNumberRangeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ private final String classDescriptor = "Lcom/android/tools/r8/naming/retrace/Main;";
+
+ private final LinePosition EXPECTED_STACK_TRACE =
+ LinePosition.stack(
+ LinePosition.create(
+ Reference.methodFromDescriptor(classDescriptor, "method3", "()V"),
+ 88,
+ 88,
+ "LineNumberRangeTest.java"),
+ LinePosition.create(
+ Reference.methodFromDescriptor(classDescriptor, "method2", "()V"),
+ 94,
+ 94,
+ "LineNumberRangeTest.java"),
+ LinePosition.create(
+ Reference.methodFromDescriptor(classDescriptor, "method1", "()V"),
+ 102,
+ 102,
+ "LineNumberRangeTest.java"),
+ LinePosition.create(
+ Reference.methodFromDescriptor(classDescriptor, "main", "()V"),
+ 108,
+ 108,
+ "LineNumberRangeTest.java"));
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public LineNumberRangeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ StackTrace expectedStackTrace =
+ testForRuntime(parameters)
+ .addProgramClassFileData(MainDump.dump())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailure()
+ .map(StackTrace::extractFromJvm);
+ assertThat(expectedStackTrace, Matchers.containsLinePositions(EXPECTED_STACK_TRACE));
+ }
+
+ @Test
+ public void testSourceFileAndLineNumberTable() throws Exception {
+ R8TestRunResult result =
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(MainDump.dump())
+ .setMode(CompilationMode.DEBUG)
+ .addKeepMainRule(Main.class)
+ .addKeepAttributes(
+ ProguardKeepAttributes.SOURCE_FILE, ProguardKeepAttributes.LINE_NUMBER_TABLE)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(
+ options -> {
+ options.enableInlining = false;
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailure();
+ // Extract actual stack trace and retraced stack trace from failed run result.
+ StackTrace actualStackTrace;
+ if (parameters.isCfRuntime()) {
+ actualStackTrace = StackTrace.extractFromJvm(result.getStdErr());
+ } else {
+ actualStackTrace =
+ StackTrace.extractFromArt(result.getStdErr(), parameters.getRuntime().asDex().getVm());
+ }
+ StackTrace retracedStackTrace = actualStackTrace.retrace(result.proguardMap());
+ assertThat(retracedStackTrace, Matchers.containsLinePositions(EXPECTED_STACK_TRACE));
+ }
+
+ // This class is generated by taking the output of InliningRetraceTest without running the
+ // line number optimizer.
+ public static class MainDump implements Opcodes {
+
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_SUPER,
+ "com/android/tools/r8/naming/retrace/Main",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitSource("LineNumberRangeTest.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(82, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "method3", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(86, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("In method3");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(87, label1);
+ methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
+ methodVisitor.visitInsn(LCONST_0);
+ methodVisitor.visitInsn(LCMP);
+ Label label2 = new Label();
+ methodVisitor.visitJumpInsn(IFLE, label2);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(88, label3);
+ methodVisitor.visitInsn(ACONST_NULL);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(90, label2);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(4, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "method2", "(I)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(92, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("In method2");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(93, label1);
+ methodVisitor.visitInsn(ICONST_0);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitIntInsn(BIPUSH, 10);
+ Label label3 = new Label();
+ methodVisitor.visitJumpInsn(IF_ICMPLT, label3);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(96, label4);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(94, label3);
+ methodVisitor.visitFrame(Opcodes.F_CHOP, 1, null, 0, null);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "com/android/tools/r8/naming/retrace/Main", "method3", "()V", false);
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(93, label5);
+ methodVisitor.visitInsn(ACONST_NULL);
+ methodVisitor.visitInsn(ATHROW);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "method1", "(Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(100, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("In method1");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(101, label1);
+ methodVisitor.visitInsn(ICONST_0);
+ methodVisitor.visitVarInsn(ISTORE, 1);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitFrame(Opcodes.F_APPEND, 1, new Object[] {Opcodes.INTEGER}, 0, null);
+ methodVisitor.visitVarInsn(ILOAD, 1);
+ methodVisitor.visitIntInsn(BIPUSH, 10);
+ Label label3 = new Label();
+ methodVisitor.visitJumpInsn(IF_ICMPGE, label3);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(102, label4);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "java/lang/Integer", "parseInt", "(Ljava/lang/String;)I", false);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "com/android/tools/r8/naming/retrace/Main", "method2", "(I)V", false);
+ Label label5 = new Label();
+ methodVisitor.visitLabel(label5);
+ methodVisitor.visitLineNumber(101, label5);
+ methodVisitor.visitIincInsn(1, 1);
+ methodVisitor.visitJumpInsn(GOTO, label2);
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(104, label3);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(107, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("In main");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(108, label1);
+ methodVisitor.visitLdcInsn("1");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/naming/retrace/Main",
+ "method1",
+ "(Ljava/lang/String;)V",
+ false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(109, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
index adc5cb8..b4b90a4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
@@ -28,7 +30,9 @@
public StackTrace expectedStackTrace;
- public void configure(R8TestBuilder builder) {}
+ public void configure(R8TestBuilder<?> builder) {}
+
+ public void inspect(CodeInspector inspector) {}
public Collection<Class<?>> getClasses() {
return ImmutableList.of(getMainClass());
@@ -42,7 +46,7 @@
expectedStackTrace =
testForJvm()
.addTestClasspath()
- .run(getMainClass())
+ .run(CfRuntime.getSystemRuntime(), getMainClass())
.assertFailure()
.map(StackTrace::extractFromJvm);
}
@@ -53,12 +57,13 @@
R8TestRunResult result =
(compat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
.setMode(mode)
- .enableProguardTestOptions()
.addProgramClasses(getClasses())
.addKeepMainRule(getMainClass())
.addKeepRules(keepRules)
.apply(this::configure)
.setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
.run(parameters.getRuntime(), getMainClass())
.assertFailure();
diff --git a/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
new file mode 100644
index 0000000..688a96f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/ServiceLoaderMultipleCallsTest.java
@@ -0,0 +1,141 @@
+// 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.rewrite;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataEntryResource;
+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.origin.Origin;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ServiceLoader;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.ZipFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ServiceLoaderMultipleCallsTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String EXPECTED_OUTPUT = StringUtils.lines("Hello World!", "Hello World!");
+
+ public interface Service {
+
+ void print();
+ }
+
+ public static class ServiceImpl implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public static class ServiceImpl2 implements Service {
+
+ @Override
+ public void print() {
+ System.out.println("Hello World 2!");
+ }
+ }
+
+ public static class MainRunner {
+
+ public static void main(String[] args) {
+ run1();
+ run2();
+ }
+
+ @NeverInline
+ public static void run1() {
+ for (Service x : ServiceLoader.load(Service.class, Service.class.getClassLoader())) {
+ x.print();
+ }
+ }
+
+ @NeverInline
+ public static void run2() {
+ for (Service x : ServiceLoader.load(Service.class, Service.class.getClassLoader())) {
+ x.print();
+ }
+ }
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ServiceLoaderMultipleCallsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRewritings() throws IOException, CompilationFailedException, ExecutionException {
+ Path path = temp.newFile("out.zip").toPath();
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ServiceLoaderMultipleCallsTest.class)
+ .addKeepMainRule(MainRunner.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .addDataEntryResources(
+ DataEntryResource.fromBytes(
+ StringUtils.lines(ServiceImpl.class.getTypeName()).getBytes(),
+ "META-INF/services/" + Service.class.getTypeName(),
+ Origin.unknown()))
+ .compile()
+ .writeToZip(path)
+ .run(parameters.getRuntime(), MainRunner.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT)
+ .inspect(
+ inspector -> {
+ // Check that we have actually rewritten the calls to ServiceLoader.load.
+ assertEquals(0, getServiceLoaderLoads(inspector, MainRunner.class));
+ // Check that the synthesize service loader class holds two methods, one for each
+ // context.
+ ClassSubject serviceLoaderMethods = inspector.clazz("$$ServiceLoaderMethods");
+ assertThat(serviceLoaderMethods, isPresent());
+ assertEquals(2, serviceLoaderMethods.allMethods().size());
+ });
+
+ // Check that we have removed the service configuration from META-INF/services.
+ ZipFile zip = new ZipFile(path.toFile());
+ assertNull(zip.getEntry("META-INF/services/" + Service.class.getTypeName()));
+ }
+
+ private static long getServiceLoaderLoads(CodeInspector inspector, Class<?> clazz) {
+ ClassSubject classSubject = inspector.clazz(clazz);
+ assertTrue(classSubject.isPresent());
+ return classSubject.allMethods().stream()
+ .mapToLong(
+ method ->
+ method
+ .streamInstructions()
+ .filter(ServiceLoaderMultipleCallsTest::isServiceLoaderLoad)
+ .count())
+ .sum();
+ }
+
+ private static boolean isServiceLoaderLoad(InstructionSubject instruction) {
+ return instruction.isInvokeStatic()
+ && instruction.getMethod().qualifiedName().contains("ServiceLoader.load");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java b/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java
new file mode 100644
index 0000000..2f95543
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/AbstractMethodOnNonAbstractClassTest.java
@@ -0,0 +1,131 @@
+// 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.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8TestCompileResult;
+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.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AbstractMethodOnNonAbstractClassTest extends TestBase {
+
+ private static final String DEX2OAT_WARNING =
+ "is abstract, but the declaring class is neither abstract nor an interface";
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public AbstractMethodOnNonAbstractClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCompat() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8Compat(parameters.getBackend())
+ .addInnerClasses(AbstractMethodOnNonAbstractClassTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+
+ // A is not made abstract in compat mode.
+ ClassSubject classSubject = compileResult.inspector().clazz(A.class);
+ assertThat(classSubject, isPresent());
+ assertFalse(classSubject.isAbstract());
+
+ // A.m() is also not made abstract in compat mode.
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("m");
+ assertThat(methodSubject, isPresent());
+ assertFalse(methodSubject.isAbstract());
+
+ if (parameters.isDexRuntime()) {
+ compileResult
+ .runDex2Oat(parameters.getRuntime())
+ .assertStderrMatches(not(containsString(DEX2OAT_WARNING)));
+ }
+
+ compileResult
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testFull() throws Exception {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(AbstractMethodOnNonAbstractClassTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+
+ // A is made abstract in full mode.
+ ClassSubject classSubject = compileResult.inspector().clazz(A.class);
+ assertThat(classSubject, isPresent());
+ assertTrue(classSubject.isAbstract());
+
+ // A.m() is also made abstract in full mode.
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("m");
+ assertThat(methodSubject, isPresent());
+ assertTrue(methodSubject.isAbstract());
+
+ if (parameters.isDexRuntime()) {
+ // There is no warning due to both A and A.m() being abstract.
+ compileResult
+ .runDex2Oat(parameters.getRuntime())
+ .assertStderrMatches(not(containsString(DEX2OAT_WARNING)));
+ }
+
+ compileResult
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A b = System.currentTimeMillis() > 0 ? new B() : new C();
+ b.m();
+ }
+ }
+
+ static class A {
+
+ // Never called directly on A, thus can be made abstract.
+ void m() {}
+ }
+
+ static class B extends A {
+
+ @Override
+ void m() {
+ System.out.println("Hello world!");
+ }
+ }
+
+ static class C extends A {
+
+ @Override
+ void m() {
+ throw new RuntimeException();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
index 105bf6b..512a9ff 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -103,8 +103,7 @@
*/
"getstatic " + client.name + "/" + obj2.name + " " + itf2.getDescriptor(),
"invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
- "return"
- );
+ "return");
final String mainClassName = mainClass.name;
String proguardConfig = StringUtils.lines(
diff --git a/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java b/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java
index 241abc5..14259ca 100644
--- a/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/array/DeadArrayLengthTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.shaking.array;
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 static org.junit.Assume.assumeTrue;
@@ -48,13 +47,8 @@
assertEquals(0, countArrayLength(nonNull));
MethodSubject nullable = main.uniqueMethodWithName("isNullable");
- if (isR8) {
- // Replaced with null-throwing code at the call site.
- assertThat(nullable, not(isPresent()));
- } else {
- assertThat(nullable, isPresent());
- assertEquals(1, countArrayLength(nullable));
- }
+ assertThat(nullable, isPresent());
+ assertEquals(isR8 ? 0 : 1, countArrayLength(nullable));
MethodSubject nullCheck = main.uniqueMethodWithName("afterNullCheck");
assertThat(nullCheck, isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java
new file mode 100644
index 0000000..632863a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumeNoSideEffectsForLibraryMethodTest.java
@@ -0,0 +1,64 @@
+// 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.shaking.assumenosideeffects;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+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.MethodSubject;
+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 AssumeNoSideEffectsForLibraryMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public AssumeNoSideEffectsForLibraryMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-assumenosideeffects class java.lang.Object { int hashCode() return 42; }")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(
+ mainMethodSubject
+ .streamInstructions()
+ .noneMatch(
+ instruction ->
+ instruction.isInvokeVirtual()
+ && instruction.getMethod().name.toString().equals("hashCode")));
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new Object().hashCode());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
index bc842e5..9c38214 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
@@ -116,7 +116,9 @@
@NeverInline
private static void testInvokeInterface(LoggerInterface logger, String message) {
- logger.debug(TAG, message);
+ if (logger != null) {
+ logger.debug(TAG, message);
+ }
}
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
index ebf633c..71614d2 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsVisibleMethodsTest.java
@@ -41,27 +41,32 @@
case RULE_THAT_REFERS_LIB_BASE:
return StringUtils.lines(
"-assumenosideeffects class " + LibraryBase.class.getTypeName() + " {",
- " *;",
+ " throwing(...);",
+ " debug(...);",
"}");
case RULE_THAT_REFERS_PRG_BASE:
return StringUtils.lines(
"-assumenosideeffects class " + ProgramBase.class.getTypeName() + " {",
- " *;",
+ " throwing(...);",
+ " debug(...);",
"}");
case RULE_THAT_REFERS_PRG_SUB:
return StringUtils.lines(
"-assumenosideeffects class " + ProgramSub.class.getTypeName() + " {",
- " *;",
+ " throwing(...);",
+ " debug(...);",
"}");
case RULE_WITH_EXTENDS_LIB_BASE:
return StringUtils.lines(
"-assumenosideeffects class * extends " + LibraryBase.class.getTypeName() + " {",
- " *;",
+ " throwing(...);",
+ " debug(...);",
"}");
case RULE_WITH_EXTENDS_PRG_BASE:
return StringUtils.lines(
"-assumenosideeffects class * extends " + ProgramBase.class.getTypeName() + " {",
- " *;",
+ " throwing(...);",
+ " debug(...);",
"}");
}
throw new Unreachable();
@@ -112,7 +117,9 @@
@Parameterized.Parameters(name = "{0} {1}")
public static Collection<Object[]> data() {
- return buildParameters(getTestParameters().withAllRuntimes().build(), TestConfig.values());
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().withAllApiLevelsAlsoForCf().build(),
+ TestConfig.values());
}
private final TestParameters parameters;
@@ -145,7 +152,7 @@
public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addLibraryFiles(libJarPath)
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addLibraryFiles(ToolHelper.getFirstSupportedAndroidJar(parameters.getApiLevel()))
.addProgramClasses(ProgramBase.class, ProgramSub.class, MAIN)
.addKeepMainRule(MAIN)
.addKeepRules(config.getKeepRule())
@@ -153,7 +160,7 @@
.enableMergeAnnotations()
.enableNeverClassInliningAnnotations()
.enableInliningAnnotations()
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.addRunClasspathFiles(parameters.isDexRuntime() ? libDexPath : libJarPath)
.run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
index 29e4588..34b1b33 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
@@ -138,7 +138,9 @@
@NeverInline
private static void testInvokeInterface(TestLogger logger, String message) {
- logger.info(TAG, message);
+ if (logger != null) {
+ logger.info(TAG, message);
+ }
}
public static void main(String... args) {
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
index fe72a94..560f6ea 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
@@ -4,15 +4,28 @@
package com.android.tools.r8.shaking.assumenosideeffects;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticPosition;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.google.common.collect.ImmutableList;
+import org.hamcrest.Matcher;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
@@ -47,13 +60,118 @@
}));
}
+ private Matcher<Diagnostic> matchAssumeNoSideEffectsWarningMessage() {
+ return diagnosticMessage(
+ containsString(
+ "The -assumenosideeffects rule matches methods on `java.lang.Object` with"
+ + " wildcards"));
+ }
+
+ private Matcher<Diagnostic> matchWarningMessageForAllProblematicMethods() {
+ return diagnosticMessage(
+ allOf(
+ containsString("void notify()"),
+ containsString("void notifyAll()"),
+ containsString("void wait()"),
+ containsString("void wait(long)"),
+ containsString("void wait(long, int)")));
+ }
+
+ private Matcher<Diagnostic> matchWarningMessageForWaitMethods() {
+ return diagnosticMessage(
+ allOf(
+ containsString("void wait()"),
+ containsString("void wait(long)"),
+ containsString("void wait(long, int)")));
+ }
+
+ private TextRange textRangeForString(String s) {
+ return new TextRange(
+ new TextPosition(0, 1, 1), new TextPosition(s.length(), 1, s.length() + 1));
+ }
+
@Test
- public void testR8() throws Exception {
+ public void testR8AllMatch() throws Exception {
testForR8(parameters.getBackend())
- .addInnerClasses(B152492625.class)
+ .addProgramClasses(TestClass.class, B.class)
.addKeepMainRule(TestClass.class)
.addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
.setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyWarnings();
+ diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
+ diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
+ })
+ .inspect(this::noCallToWait)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ }
+
+ @Test
+ public void testR8AllMatchMultipleRules() throws Exception {
+ class MyOrigin extends Origin {
+ private final String part;
+
+ public MyOrigin(String part) {
+ super(Origin.root());
+ this.part = part;
+ }
+
+ @Override
+ public String part() {
+ return part;
+ }
+ }
+
+ Origin starRuleOrigin = new MyOrigin("star rule");
+ Origin methodsRuleOrigin = new MyOrigin("methods rule");
+
+ String starRule = "-assumenosideeffects class " + B.class.getTypeName() + " { *; }";
+ String methodsRule = "-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }";
+
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .apply(
+ b ->
+ b.getBuilder().addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin))
+ .apply(
+ b ->
+ b.getBuilder()
+ .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin))
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyWarnings();
+ diagnostics.assertWarningsMatch(
+ ImmutableList.of(
+ allOf(
+ matchAssumeNoSideEffectsWarningMessage(),
+ matchWarningMessageForAllProblematicMethods(),
+ diagnosticOrigin(starRuleOrigin),
+ diagnosticPosition(textRangeForString(starRule))),
+ allOf(
+ matchAssumeNoSideEffectsWarningMessage(),
+ matchWarningMessageForAllProblematicMethods(),
+ diagnosticOrigin(methodsRuleOrigin),
+ diagnosticPosition(textRangeForString(methodsRule)))));
+ })
+ .inspect(this::noCallToWait)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ }
+
+ @Test
+ public void testR8AllMatchDontWarn() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+ .addKeepRules("-dontwarn java.lang.Object")
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::noCallToWait)
.run(parameters.getRuntime(), TestClass.class)
@@ -61,11 +179,85 @@
}
@Test
+ public void testR8AllMethodsMatch() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }")
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyWarnings();
+ diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
+ diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
+ })
+ .inspect(this::noCallToWait)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ }
+
+ @Test
+ public void testR8WaitMethodMatch() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }")
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyWarnings();
+ diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
+ diagnostics.assertWarningsMatch(matchWarningMessageForWaitMethods());
+ })
+ .inspect(this::noCallToWait)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ }
+
+ @Test
+ public void testR8WaitSpecificMethodMatch() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::noCallToWait)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ }
+
+ @Test
+ public void testR8AssumeNoSideEffectsNotConditional() throws Exception {
+ try {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-if class " + TestClass.class.getTypeName(),
+ " -assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString("Expecting '-keep' option after '-if' option")));
+ });
+ fail("Expected failed compilation");
+ } catch (CompilationFailedException e) {
+ // Expected.
+ }
+ }
+
+ @Test
public void testProguardNotRemovingWait() throws Exception {
Assume.assumeTrue(parameters.isCfRuntime());
testForProguard()
- .addInnerClasses(B152492625.class)
+ .addProgramClasses(TestClass.class, B.class)
.addKeepMainRule(TestClass.class)
.addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
.addKeepRules("-dontwarn " + B152492625.class.getTypeName())
@@ -80,7 +272,7 @@
Assume.assumeTrue(parameters.isCfRuntime());
testForProguard()
- .addInnerClasses(B152492625.class)
+ .addProgramClasses(TestClass.class, B.class)
.addKeepMainRule(TestClass.class)
.addKeepRules("-assumenosideeffects class java.lang.Object { void wait(); }")
.addKeepRules("-dontwarn " + B152492625.class.getTypeName())
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B157688676.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B157688676.java
new file mode 100644
index 0000000..85be324
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B157688676.java
@@ -0,0 +1,53 @@
+// 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.shaking.assumenosideeffects;
+
+import com.android.tools.r8.AssumeNoSideEffects;
+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 B157688676 extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public B157688676(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .enableAssumeNoSideEffectsAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ greet();
+ }
+
+ @AssumeNoSideEffects
+ static void greet() {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/NullableReturnAfterNonNullableReturnTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/NullableReturnAfterNonNullableReturnTest.java
new file mode 100644
index 0000000..e9b4f9f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/NullableReturnAfterNonNullableReturnTest.java
@@ -0,0 +1,64 @@
+// 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.shaking.assumenosideeffects;
+
+import com.android.tools.r8.AssumeNoSideEffects;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.ReprocessMethod;
+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 NullableReturnAfterNonNullableReturnTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public NullableReturnAfterNonNullableReturnTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .enableAssumeNoSideEffectsAnnotations()
+ .enableInliningAnnotations()
+ .enableReprocessMethodAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(test());
+ }
+
+ @NeverInline
+ @ReprocessMethod
+ static Object test() {
+ String s = System.currentTimeMillis() > 0 ? "Hello world!" : null;
+ checkNotNull(s);
+ return s;
+ }
+
+ @AssumeNoSideEffects
+ static void checkNotNull(Object o) {
+ if (o == null) {
+ throw new RuntimeException();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java
new file mode 100644
index 0000000..4d7e293
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesForLibraryMethodTest.java
@@ -0,0 +1,64 @@
+// 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.shaking.assumevalues;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+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.MethodSubject;
+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 AssumeValuesForLibraryMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public AssumeValuesForLibraryMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules("-assumevalues class java.lang.Object { int hashCode() return 42; }")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject mainMethodSubject = inspector.clazz(TestClass.class).mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(
+ mainMethodSubject
+ .streamInstructions()
+ .anyMatch(
+ instruction ->
+ instruction.isInvokeVirtual()
+ && instruction.getMethod().name.toString().equals("hashCode")));
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new Object().hashCode());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
index d4801ea..23a5616 100644
--- a/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/b134858535/EventPublisherTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.shaking.b134858535;
+import static org.hamcrest.CoreMatchers.containsString;
+
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -26,7 +28,13 @@
.addProgramClassFileData(EventPublisher$bDump.dump())
.addKeepClassRules(Interface.class)
.addKeepMainRule(Main.class)
+ .allowDiagnosticInfoMessages()
.setMinApi(AndroidApiLevel.L)
- .compile();
+ .compile()
+ // TODO(b/157537996): Handle JStyle lambdas with private methods.
+ .assertAllInfoMessagesMatch(
+ containsString(
+ "Unrecognized Kotlin lambda"
+ + " [com.android.tools.r8.shaking.b134858535.EventPublisher$b]"));
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index 198b253..1c9a3d0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -73,15 +73,15 @@
Assert.assertTrue(outer.getDexProgramClass().getInnerClasses().isEmpty());
ClassSubject inner = inspector.clazz("annotationremoval.OuterClass$InnerClass");
Assert.assertTrue(inner.isPresent());
- Assert.assertNull(inner.getDexProgramClass().getEnclosingMethod());
+ Assert.assertNull(inner.getDexProgramClass().getEnclosingMethodAttribute());
Assert.assertTrue(inner.getDexProgramClass().getInnerClasses().isEmpty());
ClassSubject anonymous = inspector.clazz("annotationremoval.OuterClass$1");
Assert.assertTrue(anonymous.isPresent());
- Assert.assertNull(anonymous.getDexProgramClass().getEnclosingMethod());
+ Assert.assertNull(anonymous.getDexProgramClass().getEnclosingMethodAttribute());
Assert.assertTrue(anonymous.getDexProgramClass().getInnerClasses().isEmpty());
ClassSubject local = inspector.clazz("annotationremoval.OuterClass$1LocalMagic");
Assert.assertTrue(local.isPresent());
- Assert.assertNull(local.getDexProgramClass().getEnclosingMethod());
+ Assert.assertNull(local.getDexProgramClass().getEnclosingMethodAttribute());
Assert.assertTrue(local.getDexProgramClass().getInnerClasses().isEmpty());
}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
index 912915b..ac43338 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
-import java.util.concurrent.ExecutionException;
import org.junit.Test;
public class SmaliBuildTest extends SmaliTestBase {
@@ -23,7 +22,7 @@
CodeInspector inspector = new CodeInspector(application);
ClassSubject clazz = inspector.clazz("java.lang.String");
assertEquals(present, clazz.isPresent());
- } catch (IOException | ExecutionException e) {
+ } catch (IOException e) {
throw new RuntimeException(e);
}
}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index efcc42f..10962c8 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -126,7 +126,7 @@
ClassSubject clazz = inspector.clazz(className);
assertTrue(clazz.isPresent());
return clazz.getDexProgramClass();
- } catch (IOException | ExecutionException e) {
+ } catch (IOException e) {
throw new RuntimeException(e);
}
}
@@ -135,7 +135,7 @@
try {
CodeInspector inspector = new CodeInspector(appPath);
return getMethodSubject(inspector, signature);
- } catch (IOException | ExecutionException e) {
+ } catch (IOException e) {
throw new RuntimeException(e);
}
}
diff --git a/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java
new file mode 100644
index 0000000..ab3ea7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/testing/AndroidBuildVersion.java
@@ -0,0 +1,14 @@
+// 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.testing;
+
+/**
+ * Stub class to simulate having a Build.VERSION property in headless tests.
+ *
+ * <p>Use test builder addAndroidBuildVersion() methods when used in tests.
+ */
+public class AndroidBuildVersion {
+ public static final String PROPERTY = "com.android.tools.r8.testing.AndroidBuildVersion.VERSION";
+ public static int VERSION = Integer.parseInt(System.getProperty(PROPERTY));
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 3f1ad69..1f1d744 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -337,6 +337,10 @@
});
}
+ public ClassFileTransformer unsetAbstract() {
+ return setAccessFlags(ClassAccessFlags::unsetAbstract);
+ }
+
public ClassFileTransformer setAnnotation() {
return setAccessFlags(
accessFlags -> {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 39f7c44..b2edae2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -55,7 +55,6 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@@ -72,25 +71,25 @@
public static MethodSignature MAIN =
new MethodSignature("main", "void", new String[] {"java.lang.String[]"});
- public CodeInspector(String path) throws IOException, ExecutionException {
+ public CodeInspector(String path) throws IOException {
this(Paths.get(path));
}
- public CodeInspector(Path file, String mappingFile) throws IOException, ExecutionException {
+ public CodeInspector(Path file, String mappingFile) throws IOException {
this(Collections.singletonList(file), mappingFile, null);
}
- public CodeInspector(Path file) throws IOException, ExecutionException {
+ public CodeInspector(Path file) throws IOException {
this(Collections.singletonList(file), null, null);
}
- public CodeInspector(List<Path> files) throws IOException, ExecutionException {
+ public CodeInspector(List<Path> files) throws IOException {
this(files, null, null);
}
public CodeInspector(
List<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
- throws IOException, ExecutionException {
+ throws IOException {
Path mappingPath = mappingFile != null ? Paths.get(mappingFile) : null;
if (mappingPath != null && Files.exists(mappingPath)) {
mapping = ClassNameMapper.mapperFromFile(mappingPath);
@@ -109,14 +108,14 @@
application = new ApplicationReader(input, options, timing).read();
}
- public CodeInspector(AndroidApp app) throws IOException, ExecutionException {
+ public CodeInspector(AndroidApp app) throws IOException {
this(
new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
.read(app.getProguardMapOutputData()));
}
public CodeInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
- throws IOException, ExecutionException {
+ throws IOException {
this(
new ApplicationReader(app, runOptionsConsumer(optionsConsumer), Timing.empty())
.read(app.getProguardMapOutputData()));
@@ -130,15 +129,13 @@
return internalOptions;
}
- public CodeInspector(AndroidApp app, Path proguardMapFile)
- throws IOException, ExecutionException {
+ public CodeInspector(AndroidApp app, Path proguardMapFile) throws IOException {
this(
new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
.read(StringResource.fromFile(proguardMapFile)));
}
- public CodeInspector(AndroidApp app, String proguardMapContent)
- throws IOException, ExecutionException {
+ public CodeInspector(AndroidApp app, String proguardMapContent) throws IOException {
this(
new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
.read(StringResource.fromString(proguardMapContent, Origin.unknown())));
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index c5ca6c9..bfcda02 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -50,6 +50,10 @@
boolean isConstNull();
+ default boolean isConstString() {
+ return isConstString(JumboStringMode.ALLOW);
+ }
+
boolean isConstString(JumboStringMode jumboStringMode);
boolean isConstString(String value, JumboStringMode jumboStringMode);
diff --git a/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java b/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java
new file mode 100644
index 0000000..aca08d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/workaround/InputWithAbstractMethodOnNonAbstractClassTest.java
@@ -0,0 +1,99 @@
+// 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.workaround;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbstract;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 InputWithAbstractMethodOnNonAbstractClassTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InputWithAbstractMethodOnNonAbstractClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(transformer(Greeter.class).unsetAbstract().transform())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(transformer(Greeter.class).unsetAbstract().transform())
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassAndMembersRules(Greeter.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testJVM() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(transformer(Greeter.class).unsetAbstract().transform())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ MethodSubject methodOfInterest = inspector.clazz(Greeter.class).uniqueMethodWithName("dead");
+ assertThat(methodOfInterest, isPresent());
+ if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.L)) {
+ assertThat(methodOfInterest, not(isAbstract()));
+ } else {
+ assertThat(methodOfInterest, isAbstract());
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Greeter.greet();
+ }
+ }
+
+ /*not-*/ abstract static class Greeter {
+
+ static void greet() {
+ System.out.println("Hello world!");
+ }
+
+ abstract void dead();
+ }
+}
diff --git a/third_party/chrome/monochrome_public_minimal_apks/chrome_200520.tar.gz.sha1 b/third_party/chrome/monochrome_public_minimal_apks/chrome_200520.tar.gz.sha1
new file mode 100644
index 0000000..a4d4e8f
--- /dev/null
+++ b/third_party/chrome/monochrome_public_minimal_apks/chrome_200520.tar.gz.sha1
@@ -0,0 +1 @@
+4a9de4ba961c2f0c953c0a88675f29959e8602a7
\ No newline at end of file
diff --git a/tools/chrome_data.py b/tools/chrome_data.py
index 81f2fc7..20ddb21 100644
--- a/tools/chrome_data.py
+++ b/tools/chrome_data.py
@@ -10,6 +10,8 @@
V180917_BASE = os.path.join(BASE, 'chrome_180917_ffbaa8')
V200430_BASE = os.path.join(BASE, 'chrome_200430')
+V200520_MINIMAL_BASE = os.path.join(
+ BASE, 'monochrome_public_minimal_apks', 'chrome_200520')
INPUT_JARS = [
'out/Release/gen/chrome/android/monochrome_public_apk/monochrome_public_apk.jar',
@@ -256,4 +258,22 @@
'min-api': ANDROID_N_API
},
},
+ '200520-monochrome_public_minimal_apks': {
+ 'deploy' : {
+ 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'program.jar')],
+ 'features': [
+ { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-1.jar')] },
+ { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-2.jar')] },
+ { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-3.jar')] },
+ { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-4.jar')] },
+ { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-5.jar')] },
+ { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-6.jar')] },
+ { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-7.jar')] },
+ { 'inputs': [os.path.join(V200520_MINIMAL_BASE, 'feature-8.jar')] }
+ ],
+ 'pgconf': [os.path.join(V200520_MINIMAL_BASE, 'proguard.config')],
+ 'libraries': [os.path.join(V200520_MINIMAL_BASE, 'library.jar')],
+ 'min-api': ANDROID_N_API
+ },
+ },
}
diff --git a/tools/iosched_data.py b/tools/iosched_data.py
index 10edd2b..5aac7b0 100644
--- a/tools/iosched_data.py
+++ b/tools/iosched_data.py
@@ -5,6 +5,7 @@
import os
import utils
+ANDROID_L_API = '21'
BASE = os.path.join(utils.THIRD_PARTY, 'iosched_2019')
INPUT_JARS = [
@@ -168,9 +169,10 @@
VERSIONS = {
'2019': {
'deploy' : {
- 'inputs': [os.path.join(BASE, path) for path in INPUT_JARS],
- 'pgconf': [os.path.join(BASE, 'proguard-rules.pro')],
- 'libraries': [utils.get_android_jar(28)],
+ 'inputs': [os.path.join(BASE, path) for path in INPUT_JARS],
+ 'pgconf': [os.path.join(BASE, 'proguard-rules.pro')],
+ 'libraries': [utils.get_android_jar(28)],
+ 'min-api' : ANDROID_L_API,
},
},
}
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 2f3c4fc..7b1ba0b 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -17,7 +17,7 @@
import archive_desugar_jdk_libs
import utils
-R8_DEV_BRANCH = '2.1'
+R8_DEV_BRANCH = '2.2'
R8_VERSION_FILE = os.path.join(
'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 809a1fe..1bd62e3 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -182,6 +182,10 @@
result.add_option('--cpu-list',
help='Run under \'taskset\' with these CPUs. See '
'the \'taskset\' -c option for the format')
+ result.add_option('--quiet',
+ help='Disable compiler logging',
+ default=False,
+ action='store_true')
return result.parse_args(argv)
@@ -370,7 +374,7 @@
return find_min_xmx(options, args)
if options.track_time_in_memory:
return track_time_in_memory(options, args)
- exit_code = run_with_options(options, args)
+ exit_code = run_with_options(options, args, quiet=options.quiet)
if options.expect_oom:
exit_code = 0 if exit_code == OOM_EXIT_CODE else 1
return exit_code
@@ -565,6 +569,13 @@
if options.r8_flags:
args.extend(options.r8_flags.split(' '))
+ # Feature jars.
+ features = values['features'] if 'features' in values else []
+ for i, feature in enumerate(features, start=1):
+ feature_out = os.path.join(outdir, 'feature-%d.zip' % i)
+ for feature_jar in feature['inputs']:
+ args.extend(['--feature', feature_jar, feature_out])
+
args.extend(inputs)
t0 = time.time()
diff --git a/tools/tachiyomi_data.py b/tools/tachiyomi_data.py
index 5518cea..10fafe2 100644
--- a/tools/tachiyomi_data.py
+++ b/tools/tachiyomi_data.py
@@ -5,14 +5,16 @@
import os
import utils
+ANDROID_J_API = '16'
BASE = os.path.join(utils.THIRD_PARTY, 'tachiyomi')
VERSIONS = {
'b15d2fe16864645055af6a745a62cc5566629798': {
'deploy' : {
- 'inputs': [os.path.join(BASE, 'program.jar')],
- 'pgconf': [os.path.join(BASE, 'proguard.config')],
- 'libraries': [os.path.join(BASE, 'library.jar')],
+ 'inputs': [os.path.join(BASE, 'program.jar')],
+ 'pgconf': [os.path.join(BASE, 'proguard.config')],
+ 'libraries': [os.path.join(BASE, 'library.jar')],
+ 'min-api' : ANDROID_J_API,
},
},
}