Merge commit 'a350cb5886deb3c5486c3a9ce5dd948f380ab515' into dev-release
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 7837f26..3b9bb74 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -19,11 +19,7 @@
return [output_api.PresubmitPromptWarning(msg, [])]
return []
-def CheckFormatting(input_api, output_api):
- branch = (
- check_output(['git', 'cl', 'upstream'])
- .strip()
- .replace('refs/heads/', ''))
+def CheckFormatting(input_api, output_api, branch):
results = []
for f in input_api.AffectedFiles():
path = f.LocalPath()
@@ -47,26 +43,58 @@
""" % FMT_CMD))
return results
-def CheckDeterministicDebuggingChanged(input_api, output_api):
+def CheckDeterministicDebuggingChanged(input_api, output_api, branch):
for f in input_api.AffectedFiles():
path = f.LocalPath()
if not path.endswith('InternalOptions.java'):
continue
- branch = (
- check_output(['git', 'cl', 'upstream'])
- .strip()
- .replace('refs/heads/', ''))
diff = check_output(
['git', 'diff', '--no-prefix', '-U0', branch, '--', path])
if 'DETERMINISTIC_DEBUGGING' in diff:
return [output_api.PresubmitError(diff)]
return []
-def CheckChange(input_api, output_api):
+def CheckForAddedDisassemble(input_api, output_api):
results = []
- results.extend(CheckFormatting(input_api, output_api))
+ for (file, line_nr, line) in input_api.RightHandSideLines():
+ if 'disassemble()' in line:
+ results.append(
+ output_api.PresubmitError(
+ '%s:%s %s' % (file.LocalPath(), line_nr, line)))
+ return results
+
+def CheckForCopyRight(input_api, output_api, branch):
+ results = []
+ for f in input_api.AffectedSourceFiles(None):
+ # Check if it is a new file.
+ if f.OldContents():
+ continue
+ contents = f.NewContents()
+ if (not contents) or (len(contents) == 0):
+ continue
+ if not CopyRightInContents(contents):
+ results.append(
+ output_api.PresubmitError('Could not find Copyright in file: %s' % f))
+ return results
+
+def CopyRightInContents(contents):
+ for content_line in contents:
+ if '// Copyright' in content_line:
+ return True
+ return False
+
+def CheckChange(input_api, output_api):
+ branch = (
+ check_output(['git', 'cl', 'upstream'])
+ .strip()
+ .replace('refs/heads/', ''))
+ results = []
results.extend(CheckDoNotMerge(input_api, output_api))
- results.extend(CheckDeterministicDebuggingChanged(input_api, output_api))
+ results.extend(CheckFormatting(input_api, output_api, branch))
+ results.extend(
+ CheckDeterministicDebuggingChanged(input_api, output_api, branch))
+ results.extend(CheckForAddedDisassemble(input_api, output_api))
+ results.extend(CheckForCopyRight(input_api, output_api, branch))
return results
def CheckChangeOnCommit(input_api, output_api):
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 1ce0024..615ca13 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.InitClassLens;
@@ -174,11 +175,15 @@
ClassInitializerAssertionEnablingAnalysis analysis =
new ClassInitializerAssertionEnablingAnalysis(
appInfo.dexItemFactory(), OptimizationFeedbackSimple.getInstance());
- for (DexProgramClass clazz : appInfo.classes()) {
- if (clazz.hasClassInitializer()) {
- analysis.processNewlyLiveMethod(clazz.getClassInitializer());
- }
- }
+ ThreadUtils.processItems(
+ appInfo.classes(),
+ clazz -> {
+ DexEncodedMethod classInitializer = clazz.getClassInitializer();
+ if (classInitializer != null) {
+ analysis.processNewlyLiveMethod(classInitializer);
+ }
+ },
+ executor);
}
AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 9673bc2..2f6197f 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -69,6 +69,8 @@
private DesugarGraphConsumer desugarGraphConsumer = null;
private StringConsumer desugaredLibraryKeepRuleConsumer = null;
private String synthesizedClassPrefix = "";
+ private boolean enableMainDexListCheck = true;
+ private boolean minimalMainDex = false;
private Builder() {
this(new DefaultD8DiagnosticsHandler());
@@ -174,6 +176,20 @@
return self();
}
+ @Deprecated
+ // Internal helper for supporting bazel integration.
+ Builder setEnableMainDexListCheck(boolean value) {
+ enableMainDexListCheck = value;
+ return self();
+ }
+
+ @Deprecated
+ // Internal helper for supporting bazel integration.
+ Builder setMinimalMainDex(boolean value) {
+ minimalMainDex = value;
+ return self();
+ }
+
@Override
void validate() {
Reporter reporter = getReporter();
@@ -234,6 +250,8 @@
getAssertionsConfiguration(),
getOutputInspections(),
synthesizedClassPrefix,
+ enableMainDexListCheck,
+ minimalMainDex,
getThreadCount(),
factory);
}
@@ -246,6 +264,8 @@
private final StringConsumer desugaredLibraryKeepRuleConsumer;
private final DesugaredLibraryConfiguration libraryConfiguration;
private final String synthesizedClassPrefix;
+ private final boolean enableMainDexListCheck;
+ private final boolean minimalMainDex;
private final DexItemFactory factory;
public static Builder builder() {
@@ -306,6 +326,8 @@
List<AssertionsConfiguration> assertionsConfiguration,
List<Consumer<Inspector>> outputInspections,
String synthesizedClassPrefix,
+ boolean enableMainDexListCheck,
+ boolean minimalMainDex,
int threadCount,
DexItemFactory factory) {
super(
@@ -327,6 +349,8 @@
this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
this.libraryConfiguration = libraryConfiguration;
this.synthesizedClassPrefix = synthesizedClassPrefix;
+ this.enableMainDexListCheck = enableMainDexListCheck;
+ this.minimalMainDex = minimalMainDex;
this.factory = factory;
}
@@ -337,6 +361,8 @@
desugaredLibraryKeepRuleConsumer = null;
libraryConfiguration = null;
synthesizedClassPrefix = null;
+ enableMainDexListCheck = true;
+ minimalMainDex = false;
factory = null;
}
@@ -350,7 +376,8 @@
internal.enableCfInterfaceMethodDesugaring = true;
}
internal.mainDexListConsumer = getMainDexListConsumer();
- internal.minimalMainDex = internal.debug;
+ internal.minimalMainDex = internal.debug || minimalMainDex;
+ internal.enableMainDexListCheck = enableMainDexListCheck;
internal.minApiLevel = getMinApiLevel();
internal.intermediate = intermediate;
internal.readCompileTimeAnnotations = intermediate;
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 e504e54..20216c4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -17,7 +17,7 @@
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
-import com.android.tools.r8.ir.optimize.library.LibraryMethodOptimizer;
+import com.android.tools.r8.ir.optimize.library.LibraryMemberOptimizer;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.LibraryModeledPredicate;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -57,7 +57,7 @@
// Optimizations.
private final CallSiteOptimizationInfoPropagator callSiteOptimizationInfoPropagator;
- private final LibraryMethodOptimizer libraryMethodOptimizer;
+ private final LibraryMemberOptimizer libraryMemberOptimizer;
private final ProtoShrinker protoShrinker;
// Optimization results.
@@ -102,7 +102,7 @@
this.callSiteOptimizationInfoPropagator = null;
}
- this.libraryMethodOptimizer = new LibraryMethodOptimizer(this);
+ this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
if (enableWholeProgramOptimizations() && options.protoShrinking().isProtoShrinkingEnabled()) {
this.protoShrinker = new ProtoShrinker(withLiveness());
@@ -113,7 +113,7 @@
@Override
public boolean isModeled(DexType type) {
- return libraryMethodOptimizer.isModeled(type);
+ return libraryMemberOptimizer.isModeled(type);
}
public static <T extends AppInfo> AppView<T> createForD8(T appInfo, InternalOptions options) {
@@ -247,8 +247,8 @@
return callSiteOptimizationInfoPropagator;
}
- public LibraryMethodOptimizer libraryMethodOptimizer() {
- return libraryMethodOptimizer;
+ public LibraryMemberOptimizer libraryMethodOptimizer() {
+ return libraryMemberOptimizer;
}
public ProtoShrinker protoShrinker() {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 4eb9a85..968b1c9 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import static com.android.tools.r8.utils.StringUtils.LINE_SEPARATOR;
+
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
@@ -15,7 +17,10 @@
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
+import java.io.BufferedReader;
import java.io.PrintStream;
+import java.io.StringReader;
+import java.util.stream.Collectors;
public class AssemblyWriter extends DexByteCodeWriter {
@@ -149,8 +154,14 @@
assert clazz != null : "Kotlin metadata is a class annotation";
writeKotlinMetadata(clazz, annotation, ps);
} else {
- ps.print("# ");
- ps.println(annotation);
+ String annotationString = annotation.toString();
+ String prefix = "# ";
+ ps.print(
+ new BufferedReader(new StringReader(annotationString))
+ .lines()
+ .collect(
+ Collectors.joining(
+ LINE_SEPARATOR + prefix + " ", prefix, LINE_SEPARATOR)));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 897763a..93ca81d 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -551,7 +551,7 @@
// We have no debugging info in the code.
return;
}
- int largestPrefix = 1;
+ int largestPrefix = 0;
int existingThisIndex = -1;
for (int i = 0; i < localVariables.size(); i++) {
LocalVariableInfo localVariable = localVariables.get(i);
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 bb1ee15..20337fd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -666,8 +666,7 @@
public void upgradeClassFileVersion(int version) {
checkIfObsolete();
assert version >= 0;
- assert !hasClassFileVersion() || version >= getClassFileVersion();
- classFileVersion = version;
+ classFileVersion = Math.max(classFileVersion, version);
}
public String qualifiedName() {
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 b0a86c6..b2d7140 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -398,12 +398,34 @@
public final DexType reflectiveOperationExceptionType =
createStaticallyKnownType(reflectiveOperationExceptionDescriptor);
+ public final DexType javaIoFileType = createStaticallyKnownType("Ljava/io/File;");
+ public final DexType javaMathBigIntegerType = createStaticallyKnownType("Ljava/math/BigInteger;");
+ public final DexType javaNioByteOrderType = createStaticallyKnownType("Ljava/nio/ByteOrder;");
+ public final DexType javaUtilCollectionsType =
+ createStaticallyKnownType("Ljava/util/Collections;");
+ public final DexType javaUtilComparatorType = createStaticallyKnownType("Ljava/util/Comparator;");
+ public final DexType javaUtilConcurrentTimeUnitType =
+ createStaticallyKnownType("Ljava/util/concurrent/TimeUnit;");
+ public final DexType javaUtilListType = createStaticallyKnownType("Ljava/util/List;");
+ public final DexType javaUtilLocaleType = createStaticallyKnownType("Ljava/util/Locale;");
+ public final DexType javaUtilLoggingLevelType =
+ createStaticallyKnownType("Ljava/util/logging/Level;");
public final DexType javaUtilLoggingLoggerType =
createStaticallyKnownType("Ljava/util/logging/Logger;");
- public final DexType androidUtilLogType = createStaticallyKnownType("Landroid/util/Log;");
+ public final DexType javaUtilSetType = createStaticallyKnownType("Ljava/util/Set;");
+ public final DexType androidOsBuildType = createStaticallyKnownType("Landroid/os/Build;");
public final DexType androidOsBuildVersionType =
createStaticallyKnownType("Landroid/os/Build$VERSION;");
+ public final DexType androidOsBundleType = createStaticallyKnownType("Landroid/os/Bundle;");
+ public final DexType androidOsParcelableCreatorType =
+ createStaticallyKnownType("Landroid/os/Parcelable$Creator;");
+ public final DexType androidSystemOsConstantsType =
+ createStaticallyKnownType("Landroid/system/OsConstants;");
+ public final DexType androidUtilLogType = createStaticallyKnownType("Landroid/util/Log;");
+ public final DexType androidUtilPropertyType =
+ createStaticallyKnownType("Landroid/util/Property;");
+ public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
public final DexString nestConstructorDescriptor =
createString("L" + NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME + ";");
@@ -419,12 +441,13 @@
public final StringBuildingMethods stringBufferMethods =
new StringBuildingMethods(stringBufferType);
public final BooleanMembers booleanMembers = new BooleanMembers();
+ public final FloatMembers floatMembers = new FloatMembers();
+ public final IntegerMembers integerMembers = new IntegerMembers();
public final ObjectsMethods objectsMethods = new ObjectsMethods();
public final ObjectMembers objectMembers = new ObjectMembers();
- public final StringMethods stringMethods = new StringMethods();
- public final LongMethods longMethods = new LongMethods();
+ public final StringMembers stringMembers = new StringMembers();
+ public final LongMembers longMembers = new LongMembers();
public final DoubleMethods doubleMethods = new DoubleMethods();
- public final JavaUtilArraysMethods utilArraysMethods = new JavaUtilArraysMethods();
public final ThrowableMethods throwableMethods = new ThrowableMethods();
public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
public final ClassMethods classMethods = new ClassMethods();
@@ -441,6 +464,51 @@
public final PolymorphicMethods polymorphicMethods = new PolymorphicMethods();
public final ProxyMethods proxyMethods = new ProxyMethods();
+ // android.**
+ public final AndroidOsBuildMembers androidOsBuildMembers = new AndroidOsBuildMembers();
+ public final AndroidOsBuildVersionMembers androidOsBuildVersionMembers =
+ new AndroidOsBuildVersionMembers();
+ public final AndroidOsBundleMembers androidOsBundleMembers = new AndroidOsBundleMembers();
+ public final AndroidSystemOsConstantsMembers androidSystemOsConstantsMembers =
+ new AndroidSystemOsConstantsMembers();
+ public final AndroidViewViewMembers androidViewViewMembers = new AndroidViewViewMembers();
+
+ // java.**
+ public final JavaIoFileMembers javaIoFileMembers = new JavaIoFileMembers();
+ public final JavaMathBigIntegerMembers javaMathBigIntegerMembers =
+ new JavaMathBigIntegerMembers();
+ public final JavaNioByteOrderMembers javaNioByteOrderMembers = new JavaNioByteOrderMembers();
+ public final JavaUtilArraysMethods javaUtilArraysMethods = new JavaUtilArraysMethods();
+ public final JavaUtilComparatorMembers javaUtilComparatorMembers =
+ new JavaUtilComparatorMembers();
+ public final JavaUtilConcurrentTimeUnitMembers javaUtilConcurrentTimeUnitMembers =
+ new JavaUtilConcurrentTimeUnitMembers();
+ public final JavaUtilLocaleMembers javaUtilLocaleMembers = new JavaUtilLocaleMembers();
+ public final JavaUtilLoggingLevelMembers javaUtilLoggingLevelMembers =
+ new JavaUtilLoggingLevelMembers();
+
+ public final List<LibraryMembers> libraryMembersCollection =
+ ImmutableList.of(
+ booleanMembers,
+ floatMembers,
+ integerMembers,
+ longMembers,
+ stringMembers,
+ // android.**
+ androidOsBuildMembers,
+ androidOsBuildVersionMembers,
+ androidOsBundleMembers,
+ androidSystemOsConstantsMembers,
+ androidViewViewMembers,
+ // java.**
+ javaIoFileMembers,
+ javaMathBigIntegerMembers,
+ javaNioByteOrderMembers,
+ javaUtilComparatorMembers,
+ javaUtilConcurrentTimeUnitMembers,
+ javaUtilLocaleMembers,
+ javaUtilLoggingLevelMembers);
+
public final DexString twrCloseResourceMethodName = createString("$closeResource");
public final DexProto twrCloseResourceMethodProto =
createProto(voidType, throwableType, autoCloseableType);
@@ -604,7 +672,7 @@
objectsMethods.requireNonNull,
objectsMethods.requireNonNullWithMessage,
objectsMethods.requireNonNullWithMessageSupplier,
- stringMethods.valueOf);
+ stringMembers.valueOf);
// We assume library methods listed here are `public`, i.e., free from visibility side effects.
// If not, that library method should not be added here because it literally has side effects.
@@ -672,7 +740,12 @@
return dexMethod == metafactoryMethod || dexMethod == metafactoryAltMethod;
}
- public class BooleanMembers {
+ public interface LibraryMembers {
+
+ void forEachFinalField(Consumer<DexField> consumer);
+ }
+
+ public class BooleanMembers implements LibraryMembers {
public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
public final DexField TRUE = createField(boxedBooleanType, boxedBooleanType, "TRUE");
@@ -684,16 +757,291 @@
createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
private BooleanMembers() {}
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(FALSE);
+ consumer.accept(TRUE);
+ consumer.accept(TYPE);
+ }
}
- public class LongMethods {
+ public class AndroidOsBuildMembers implements LibraryMembers {
+
+ public final DexField BOOTLOADER = createField(androidOsBuildType, stringType, "BOOTLOADER");
+ public final DexField BRAND = createField(androidOsBuildType, stringType, "BRAND");
+ public final DexField CPU_ABI = createField(androidOsBuildType, stringType, "CPU_ABI");
+ public final DexField CPU_ABI2 = createField(androidOsBuildType, stringType, "CPU_ABI2");
+ public final DexField DEVICE = createField(androidOsBuildType, stringType, "DEVICE");
+ public final DexField DISPLAY = createField(androidOsBuildType, stringType, "DISPLAY");
+ public final DexField FINGERPRINT = createField(androidOsBuildType, stringType, "FINGERPRINT");
+ public final DexField HARDWARE = createField(androidOsBuildType, stringType, "HARDWARE");
+ public final DexField MANUFACTURER =
+ createField(androidOsBuildType, stringType, "MANUFACTURER");
+ public final DexField MODEL = createField(androidOsBuildType, stringType, "MODEL");
+ public final DexField PRODUCT = createField(androidOsBuildType, stringType, "PRODUCT");
+ public final DexField SERIAL = createField(androidOsBuildType, stringType, "SERIAL");
+ public final DexField SUPPORTED_32_BIT_ABIS =
+ createField(androidOsBuildType, stringArrayType, "SUPPORTED_32_BIT_ABIS");
+ public final DexField SUPPORTED_64_BIT_ABIS =
+ createField(androidOsBuildType, stringArrayType, "SUPPORTED_64_BIT_ABIS");
+ public final DexField SUPPORTED_ABIS =
+ createField(androidOsBuildType, stringArrayType, "SUPPORTED_ABIS");
+ public final DexField TIME = createField(androidOsBuildType, longType, "TIME");
+ public final DexField TYPE = createField(androidOsBuildType, stringType, "TYPE");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(BOOTLOADER);
+ consumer.accept(BRAND);
+ consumer.accept(CPU_ABI);
+ consumer.accept(CPU_ABI2);
+ consumer.accept(DEVICE);
+ consumer.accept(DISPLAY);
+ consumer.accept(FINGERPRINT);
+ consumer.accept(HARDWARE);
+ consumer.accept(MANUFACTURER);
+ consumer.accept(MODEL);
+ consumer.accept(PRODUCT);
+ consumer.accept(SERIAL);
+ consumer.accept(SUPPORTED_32_BIT_ABIS);
+ consumer.accept(SUPPORTED_64_BIT_ABIS);
+ consumer.accept(SUPPORTED_ABIS);
+ consumer.accept(TIME);
+ consumer.accept(TYPE);
+ }
+ }
+
+ public class AndroidOsBuildVersionMembers implements LibraryMembers {
+
+ public final DexField CODENAME = createField(androidOsBuildVersionType, stringType, "CODENAME");
+ public final DexField RELEASE = createField(androidOsBuildVersionType, stringType, "RELEASE");
+ public final DexField SDK = createField(androidOsBuildVersionType, stringType, "SDK");
+ public final DexField SDK_INT = createField(androidOsBuildVersionType, intType, "SDK_INT");
+ public final DexField SECURITY_PATCH =
+ createField(androidOsBuildVersionType, stringType, "SECURITY_PATCH");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(CODENAME);
+ consumer.accept(RELEASE);
+ consumer.accept(SDK);
+ consumer.accept(SDK_INT);
+ consumer.accept(SECURITY_PATCH);
+ }
+ }
+
+ public class AndroidOsBundleMembers implements LibraryMembers {
+
+ public final DexField CREATOR =
+ createField(androidOsBundleType, androidOsParcelableCreatorType, "CREATOR");
+ public final DexField EMPTY = createField(androidOsBundleType, androidOsBundleType, "EMPTY");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(CREATOR);
+ consumer.accept(EMPTY);
+ }
+ }
+
+ public class AndroidSystemOsConstantsMembers implements LibraryMembers {
+
+ public final DexField S_IRUSR = createField(androidSystemOsConstantsType, intType, "S_IRUSR");
+ public final DexField S_IXUSR = createField(androidSystemOsConstantsType, intType, "S_IXUSR");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(S_IRUSR);
+ consumer.accept(S_IXUSR);
+ }
+ }
+
+ public class AndroidViewViewMembers implements LibraryMembers {
+
+ public final DexField TRANSLATION_Z =
+ createField(androidViewViewType, androidUtilPropertyType, "TRANSLATION_Z");
+ public final DexField EMPTY_STATE_SET =
+ createField(androidViewViewType, intArrayType, "EMPTY_STATE_SET");
+ public final DexField ENABLED_STATE_SET =
+ createField(androidViewViewType, intArrayType, "ENABLED_STATE_SET");
+ public final DexField PRESSED_ENABLED_STATE_SET =
+ createField(androidViewViewType, intArrayType, "PRESSED_ENABLED_STATE_SET");
+ public final DexField SELECTED_STATE_SET =
+ createField(androidViewViewType, intArrayType, "SELECTED_STATE_SET");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(TRANSLATION_Z);
+ consumer.accept(EMPTY_STATE_SET);
+ consumer.accept(ENABLED_STATE_SET);
+ consumer.accept(PRESSED_ENABLED_STATE_SET);
+ consumer.accept(SELECTED_STATE_SET);
+ }
+ }
+
+ public class FloatMembers implements LibraryMembers {
+
+ public final DexField TYPE = createField(boxedFloatType, classType, "TYPE");
+
+ private FloatMembers() {}
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(TYPE);
+ }
+ }
+
+ public class JavaIoFileMembers implements LibraryMembers {
+
+ public final DexField pathSeparator = createField(javaIoFileType, stringType, "pathSeparator");
+ public final DexField separator = createField(javaIoFileType, stringType, "separator");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(pathSeparator);
+ consumer.accept(separator);
+ }
+ }
+
+ public class JavaMathBigIntegerMembers implements LibraryMembers {
+
+ public final DexField ONE = createField(javaMathBigIntegerType, javaMathBigIntegerType, "ONE");
+ public final DexField ZERO =
+ createField(javaMathBigIntegerType, javaMathBigIntegerType, "ZERO");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(ONE);
+ consumer.accept(ZERO);
+ }
+ }
+
+ public class JavaNioByteOrderMembers implements LibraryMembers {
+
+ public final DexField LITTLE_ENDIAN =
+ createField(javaNioByteOrderType, javaNioByteOrderType, "LITTLE_ENDIAN");
+ public final DexField BIG_ENDIAN =
+ createField(javaNioByteOrderType, javaNioByteOrderType, "BIG_ENDIAN");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(LITTLE_ENDIAN);
+ consumer.accept(BIG_ENDIAN);
+ }
+ }
+
+ public class JavaUtilArraysMethods {
+
+ public final DexMethod asList;
+
+ private JavaUtilArraysMethods() {
+ asList =
+ createMethod(
+ arraysDescriptor,
+ createString("asList"),
+ listDescriptor,
+ new DexString[] {objectArrayDescriptor});
+ }
+ }
+
+ public class JavaUtilComparatorMembers implements LibraryMembers {
+
+ public final DexField EMPTY_LIST =
+ createField(javaUtilCollectionsType, javaUtilListType, "EMPTY_LIST");
+ public final DexField EMPTY_SET =
+ createField(javaUtilCollectionsType, javaUtilSetType, "EMPTY_SET");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(EMPTY_LIST);
+ consumer.accept(EMPTY_SET);
+ }
+ }
+
+ public class JavaUtilConcurrentTimeUnitMembers implements LibraryMembers {
+
+ public final DexField DAYS =
+ createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "DAYS");
+ public final DexField HOURS =
+ createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "HOURS");
+ public final DexField MICROSECONDS =
+ createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "MICROSECONDS");
+ public final DexField MILLISECONDS =
+ createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "MILLISECONDS");
+ public final DexField MINUTES =
+ createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "MINUTES");
+ public final DexField NANOSECONDS =
+ createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "NANOSECONDS");
+ public final DexField SECONDS =
+ createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "SECONDS");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(DAYS);
+ consumer.accept(HOURS);
+ consumer.accept(MICROSECONDS);
+ consumer.accept(MILLISECONDS);
+ consumer.accept(MINUTES);
+ consumer.accept(NANOSECONDS);
+ consumer.accept(SECONDS);
+ }
+ }
+
+ public class JavaUtilLocaleMembers implements LibraryMembers {
+
+ public final DexField ENGLISH = createField(javaUtilLocaleType, javaUtilLocaleType, "ENGLISH");
+ public final DexField ROOT = createField(javaUtilLocaleType, javaUtilLocaleType, "ROOT");
+ public final DexField US = createField(javaUtilLocaleType, javaUtilLocaleType, "US");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(ENGLISH);
+ consumer.accept(ROOT);
+ consumer.accept(US);
+ }
+ }
+
+ public class JavaUtilLoggingLevelMembers implements LibraryMembers {
+
+ public final DexField CONFIG =
+ createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "CONFIG");
+ public final DexField FINE =
+ createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "FINE");
+ public final DexField FINER =
+ createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "FINER");
+ public final DexField FINEST =
+ createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "FINEST");
+ public final DexField SEVERE =
+ createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "SEVERE");
+ public final DexField WARNING =
+ createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "WARNING");
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(CONFIG);
+ consumer.accept(FINE);
+ consumer.accept(FINER);
+ consumer.accept(FINEST);
+ consumer.accept(SEVERE);
+ consumer.accept(WARNING);
+ }
+ }
+
+ public class LongMembers implements LibraryMembers {
+
+ public final DexField TYPE = createField(boxedLongType, classType, "TYPE");
public final DexMethod compare;
- private LongMethods() {
+ private LongMembers() {
compare = createMethod(boxedLongDescriptor,
createString("compare"), intDescriptor, new DexString[]{longDescriptor, longDescriptor});
}
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(TYPE);
+ }
}
public class DoubleMethods {
@@ -710,17 +1058,13 @@
}
}
- public class JavaUtilArraysMethods {
+ public class IntegerMembers implements LibraryMembers {
- public final DexMethod asList;
+ public final DexField TYPE = createField(boxedIntType, classType, "TYPE");
- private JavaUtilArraysMethods() {
- asList =
- createMethod(
- arraysDescriptor,
- createString("asList"),
- listDescriptor,
- new DexString[] {objectArrayDescriptor});
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(TYPE);
}
}
@@ -1072,7 +1416,11 @@
}
}
- public class StringMethods {
+ public class StringMembers implements LibraryMembers {
+
+ public final DexField CASE_INSENSITIVE_ORDER =
+ createField(stringType, javaUtilComparatorType, "CASE_INSENSITIVE_ORDER");
+
public final DexMethod isEmpty;
public final DexMethod length;
@@ -1098,7 +1446,7 @@
public final DexMethod trim = createMethod(stringType, createProto(stringType), trimName);
- private StringMethods() {
+ private StringMembers() {
isEmpty = createMethod(
stringDescriptor, isEmptyMethodName, booleanDescriptor, DexString.EMPTY_ARRAY);
length = createMethod(
@@ -1145,6 +1493,11 @@
intern = createMethod(
stringDescriptor, internMethodName, stringDescriptor, DexString.EMPTY_ARRAY);
}
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(CASE_INSENSITIVE_ORDER);
+ }
}
public class StringBuildingMethods {
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 97d49a5..3b4d284 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.utils.DescriptorUtils.getClassBinaryNameFromDescriptor;
import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -218,10 +217,30 @@
}
}
- // TODO(b/129925954): better structures for a circle of
- // TypeSignature - FieldTypeSignature - ClassTypeSignature - TypeArgument
+ public enum WildcardIndicator {
+ NOT_AN_ARGUMENT,
+ NONE,
+ NEGATIVE,
+ POSITIVE
+ }
+
public abstract static class FieldTypeSignature
extends TypeSignature implements DexDefinitionSignature<DexEncodedField> {
+
+ private final WildcardIndicator wildcardIndicator;
+
+ private FieldTypeSignature(WildcardIndicator wildcardIndicator) {
+ this.wildcardIndicator = wildcardIndicator;
+ }
+
+ public final boolean isArgument() {
+ return wildcardIndicator != WildcardIndicator.NOT_AN_ARGUMENT;
+ }
+
+ public WildcardIndicator getWildcardIndicator() {
+ return wildcardIndicator;
+ }
+
@Override
public boolean isFieldTypeSignature() {
return true;
@@ -259,6 +278,32 @@
public boolean isUnknown() {
return this == ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
}
+
+ public abstract FieldTypeSignature asArgument(WildcardIndicator indicator);
+
+ public boolean isStar() {
+ return false;
+ }
+ }
+
+ private static final class StarFieldTypeSignature extends FieldTypeSignature {
+
+ private static final StarFieldTypeSignature STAR_FIELD_TYPE_SIGNATURE =
+ new StarFieldTypeSignature();
+
+ private StarFieldTypeSignature() {
+ super(WildcardIndicator.NONE);
+ }
+
+ @Override
+ public FieldTypeSignature asArgument(WildcardIndicator indicator) {
+ throw new Unreachable("Should not be called");
+ }
+
+ @Override
+ public boolean isStar() {
+ return true;
+ }
}
public static class ClassTypeSignature extends FieldTypeSignature {
@@ -268,7 +313,6 @@
final DexType type;
// E.g., for Map<K, V>, a signature will indicate what types are for K and V.
// Note that this could be nested, e.g., Map<K, Consumer<V>>.
- // TODO(b/129925954): What about * ?
final List<FieldTypeSignature> typeArguments;
// TODO(b/129925954): towards immutable structure?
@@ -277,10 +321,17 @@
ClassTypeSignature innerTypeSignature;
ClassTypeSignature(DexType type, List<FieldTypeSignature> typeArguments) {
+ this(type, typeArguments, WildcardIndicator.NOT_AN_ARGUMENT);
+ }
+
+ private ClassTypeSignature(
+ DexType type, List<FieldTypeSignature> typeArguments, WildcardIndicator indicator) {
+ super(indicator);
assert type != null;
assert typeArguments != null;
this.type = type;
this.typeArguments = typeArguments;
+ assert typeArguments.stream().allMatch(FieldTypeSignature::isArgument);
}
public DexType type() {
@@ -302,6 +353,15 @@
}
@Override
+ public ClassTypeSignature asArgument(WildcardIndicator indicator) {
+ assert indicator != WildcardIndicator.NOT_AN_ARGUMENT;
+ ClassTypeSignature argument = new ClassTypeSignature(type, typeArguments, indicator);
+ argument.innerTypeSignature = this.innerTypeSignature;
+ argument.enclosingTypeSignature = this.enclosingTypeSignature;
+ return argument;
+ }
+
+ @Override
public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) {
return new ArrayTypeSignature(this);
}
@@ -311,32 +371,18 @@
outer.innerTypeSignature = inner;
inner.enclosingTypeSignature = outer;
}
-
- // TODO(b/129925954): rewrite GenericSignatureRewriter with this pattern?
- public interface Converter<R> {
- R init();
- R visitType(DexType type, R result);
- R visitTypeArgument(FieldTypeSignature typeArgument, R result);
- R visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, R result);
- }
-
- public <R> R convert(Converter<R> converter) {
- R result = converter.init();
- result = converter.visitType(type, result);
- for (FieldTypeSignature typeArgument : typeArguments) {
- result = converter.visitTypeArgument(typeArgument, result);
- }
- if (innerTypeSignature != null) {
- result = converter.visitInnerTypeSignature(innerTypeSignature, result);
- }
- return result;
- }
}
public static class ArrayTypeSignature extends FieldTypeSignature {
+
final TypeSignature elementSignature;
ArrayTypeSignature(TypeSignature elementSignature) {
+ this(elementSignature, WildcardIndicator.NOT_AN_ARGUMENT);
+ }
+
+ private ArrayTypeSignature(TypeSignature elementSignature, WildcardIndicator indicator) {
+ super(indicator);
assert elementSignature != null;
this.elementSignature = elementSignature;
}
@@ -356,6 +402,12 @@
}
@Override
+ public FieldTypeSignature asArgument(WildcardIndicator indicator) {
+ assert indicator != WildcardIndicator.NOT_AN_ARGUMENT;
+ return new ArrayTypeSignature(elementSignature, indicator);
+ }
+
+ @Override
public TypeSignature toArrayTypeSignature(AppView<?> appView) {
return new ArrayTypeSignature(this);
}
@@ -367,9 +419,15 @@
}
public static class TypeVariableSignature extends FieldTypeSignature {
+
final String typeVariable;
private TypeVariableSignature(String typeVariable) {
+ this(typeVariable, WildcardIndicator.NOT_AN_ARGUMENT);
+ }
+
+ private TypeVariableSignature(String typeVariable, WildcardIndicator indicator) {
+ super(indicator);
assert typeVariable != null;
this.typeVariable = typeVariable;
}
@@ -385,6 +443,12 @@
}
@Override
+ public FieldTypeSignature asArgument(WildcardIndicator indicator) {
+ assert indicator != WildcardIndicator.NOT_AN_ARGUMENT;
+ return new TypeVariableSignature(typeVariable, indicator);
+ }
+
+ @Override
public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) {
return new ArrayTypeSignature(this);
}
@@ -617,9 +681,6 @@
try {
setInput(signature);
return parseClassSignature();
- } catch (Unimplemented e) {
- // TODO(b/129925954): Should not catch this once fully implemented
- return ClassSignature.UNKNOWN_CLASS_SIGNATURE;
} catch (GenericSignatureFormatError e) {
throw e;
} catch (Throwable t) {
@@ -634,9 +695,6 @@
try {
setInput(signature);
return parseMethodTypeSignature();
- } catch (Unimplemented e) {
- // TODO(b/129925954): Should not catch this once fully implemented
- return MethodTypeSignature.UNKNOWN_METHOD_TYPE_SIGNATURE;
} catch (GenericSignatureFormatError e) {
throw e;
} catch (Throwable t) {
@@ -651,9 +709,6 @@
try {
setInput(signature);
return parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
- } catch (Unimplemented e) {
- // TODO(b/129925954): Should not catch this once fully implemented
- return ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
} catch (GenericSignatureFormatError e) {
throw e;
} catch (Throwable t) {
@@ -877,15 +932,18 @@
// TypeArgument ::= (["+" | "-"] FieldTypeSignature) | "*".
if (symbol == '*') {
scanSymbol();
- throw new Unimplemented("GenericSignature.TypeArgument *");
+ return StarFieldTypeSignature.STAR_FIELD_TYPE_SIGNATURE;
} else if (symbol == '+') {
scanSymbol();
- return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
+ return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION)
+ .asArgument(WildcardIndicator.POSITIVE);
} else if (symbol == '-') {
scanSymbol();
- return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
+ return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION)
+ .asArgument(WildcardIndicator.NEGATIVE);
} else {
- return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
+ return parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION)
+ .asArgument(WildcardIndicator.NONE);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 283777f..fbecfef 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -388,7 +388,8 @@
}
},
lambda -> {
- assert resolvedHolder.isInterface();
+ assert resolvedHolder.isInterface()
+ || resolvedHolder.type == appInfo.dexItemFactory().objectType;
LookupTarget target = lookupVirtualDispatchTarget(lambda, appInfo);
if (target != null) {
if (target.isLambdaTarget()) {
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 05416df..f417d65 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
@@ -20,16 +20,12 @@
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilledData;
import com.android.tools.r8.ir.code.NewInstance;
-import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.LongInterval;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
-import java.util.ArrayList;
-import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -119,9 +115,6 @@
if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
return false;
}
- if (isAliasOfValueThatIsIndependentOfEnvironment(root, assumedNotToDependOnEnvironment)) {
- return false;
- }
return true;
} finally {
boolean changed = visited.remove(root);
@@ -414,49 +407,4 @@
return false;
}
-
- private boolean isAliasOfValueThatIsIndependentOfEnvironment(
- Value value, Set<Value> assumedNotToDependOnEnvironment) {
- // If we are inside a class initializer, and we are reading a final field of the enclosing
- // class, then check if there is a single write to that field in the class initializer, and that
- // the value being written into that field does not depend on the environment.
- //
- // The reason why we do not currently treat final fields that are written in multiple places is
- // that the value of the field could then be dependent on the environment due to the control
- // flow.
- if (code.method.isClassInitializer()) {
- assert !value.hasAliasedValue();
- if (value.isPhi()) {
- return false;
- }
- Instruction definition = value.definition;
- if (definition.isStaticGet()) {
- StaticGet staticGet = definition.asStaticGet();
- DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
- if (field != null && field.holder() == context) {
- List<StaticPut> finalFieldPuts = computeFinalFieldPuts().get(field);
- if (finalFieldPuts == null || finalFieldPuts.size() != 1) {
- return false;
- }
- StaticPut staticPut = ListUtils.first(finalFieldPuts);
- return !valueMayDependOnEnvironment(staticPut.value(), assumedNotToDependOnEnvironment);
- }
- }
- }
- return false;
- }
-
- private Map<DexEncodedField, List<StaticPut>> computeFinalFieldPuts() {
- assert code.method.isClassInitializer();
- if (finalFieldPuts == null) {
- finalFieldPuts = new IdentityHashMap<>();
- for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
- DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
- if (field != null && field.holder() == context && field.isFinal()) {
- finalFieldPuts.computeIfAbsent(field, ignore -> new ArrayList<>()).add(staticPut);
- }
- }
- }
- return finalFieldPuts;
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
index 3e34256..6c1136f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
@@ -65,8 +65,7 @@
}
}
- // TODO(b/150409786): Move all code rewriting passes into the lens code rewriter.
- // assert new TypeAnalysis(appView).verifyValuesUpToDate(affectedPhis);
+ assert new TypeAnalysis(appView).verifyValuesUpToDate(affectedPhis);
// Now that the types of all transitively type affected phis have been reset, we can
// perform a narrowing, starting from the values that are affected by those phis.
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 52aed30..9dea6f8 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
@@ -184,6 +184,28 @@
onControlFlowEdgesMayChangeListeners.add(listener);
}
+ public boolean hasUniqueSuccessor() {
+ return successors.size() == 1;
+ }
+
+ public boolean hasUniqueNormalSuccessor() {
+ return numberOfNormalSuccessors() == 1;
+ }
+
+ public boolean hasUniqueNormalSuccessorWithUniquePredecessor() {
+ return hasUniqueNormalSuccessor() && getUniqueNormalSuccessor().getPredecessors().size() == 1;
+ }
+
+ public BasicBlock getUniqueSuccessor() {
+ assert hasUniqueSuccessor();
+ return successors.get(0);
+ }
+
+ public BasicBlock getUniqueNormalSuccessor() {
+ assert hasUniqueNormalSuccessor();
+ return ListUtils.last(successors);
+ }
+
public List<BasicBlock> getSuccessors() {
return Collections.unmodifiableList(successors);
}
@@ -200,17 +222,27 @@
return true;
}
+ public void forEachNormalSuccessor(Consumer<BasicBlock> consumer) {
+ for (int i = successors.size() - numberOfNormalSuccessors(); i < successors.size(); i++) {
+ consumer.accept(successors.get(i));
+ }
+ }
+
+ public boolean hasNormalSuccessor(BasicBlock block) {
+ for (int i = successors.size() - numberOfNormalSuccessors(); i < successors.size(); i++) {
+ if (successors.get(i) == block) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public List<BasicBlock> getNormalSuccessors() {
if (!hasCatchHandlers()) {
return successors;
}
- Set<Integer> handlers = catchHandlers.getUniqueTargets();
ImmutableList.Builder<BasicBlock> normals = ImmutableList.builder();
- for (int i = 0; i < successors.size(); i++) {
- if (!handlers.contains(i)) {
- normals.add(successors.get(i));
- }
- }
+ forEachNormalSuccessor(normals::add);
return normals.build();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 63f6a89..f356690 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -3,19 +3,21 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
+import com.android.tools.r8.graph.LookupResult;
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling;
+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.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -24,7 +26,8 @@
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
-import com.google.common.collect.ImmutableList;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Sets;
import java.util.BitSet;
import java.util.Collection;
@@ -77,63 +80,48 @@
// TODO(b/140204899): Refactor lookup methods to be defined in a single place.
public Collection<DexEncodedMethod> lookupTargets(
- AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
- // Leverage exact receiver type if available.
- DexEncodedMethod singleTarget = lookupSingleTarget(appView, invocationContext);
- if (singleTarget != null) {
- return ImmutableList.of(singleTarget);
- }
- if (!isInvokeMethodWithDynamicDispatch() || !appView.appInfo().hasLiveness()) {
+ AppView<AppInfoWithLiveness> appView, DexType invocationContext) {
+ if (!getInvokedMethod().holder.isClassType()) {
return null;
}
- LookupResultSuccess lookupResult =
- appView
- .appInfo()
- .resolveMethod(method.holder, method)
- .lookupVirtualDispatchTargets(
- appView.definitionForProgramType(invocationContext),
- appView.withLiveness().appInfo())
- .asLookupResultSuccess();
- if (lookupResult == null || lookupResult.isEmpty()) {
- return null;
+ if (!isInvokeMethodWithDynamicDispatch()) {
+ DexEncodedMethod singleTarget = lookupSingleTarget(appView, invocationContext);
+ return singleTarget != null ? SetUtils.newIdentityHashSet(singleTarget) : null;
}
- assert lookupResult.hasMethodTargets();
- if (lookupResult.hasLambdaTargets()) {
- // TODO(b/150277553): Support lambda targets.
- return null;
+ DexProgramClass refinedReceiverUpperBound =
+ asProgramClassOrNull(
+ appView.definitionFor(
+ TypeAnalysis.getRefinedReceiverType(appView, asInvokeMethodWithReceiver())));
+ DexProgramClass refinedReceiverLowerBound = null;
+ ClassTypeElement refinedReceiverLowerBoundType =
+ asInvokeMethodWithReceiver().getReceiver().getDynamicLowerBoundType(appView);
+ if (refinedReceiverLowerBoundType != null) {
+ refinedReceiverLowerBound =
+ asProgramClassOrNull(appView.definitionFor(refinedReceiverLowerBoundType.getClassType()));
}
- DexType staticReceiverType = getInvokedMethod().holder;
- DexType refinedReceiverType =
- TypeAnalysis.getRefinedReceiverType(
- appView.withLiveness(), this.asInvokeMethodWithReceiver());
- // TODO(b/140204899): Instead of reprocessing here, pass refined receiver to lookup.
- // Leverage refined receiver type if available.
- if (refinedReceiverType != staticReceiverType) {
- ResolutionResult refinedResolution =
- appView.appInfo().resolveMethod(refinedReceiverType, method);
- if (refinedResolution.isSingleResolution()) {
- DexEncodedMethod refinedTarget = refinedResolution.getSingleTarget();
- Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
- lookupResult.forEach(
- methodTarget -> {
- DexEncodedMethod target = methodTarget.getMethod();
- if (target == refinedTarget
- || appView.isSubtype(target.holder(), refinedReceiverType).isPossiblyTrue()) {
- result.add(target);
- }
- },
- lambdaTarget -> {
- throw new Unreachable();
- });
- return result;
- }
- // If resolution at the refined type fails, conservatively return the full set of targets.
+ ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
+ LookupResult lookupResult;
+ if (refinedReceiverUpperBound != null) {
+ lookupResult =
+ resolutionResult.lookupVirtualDispatchTargets(
+ appView.definitionForProgramType(invocationContext),
+ appView.withLiveness().appInfo(),
+ refinedReceiverUpperBound,
+ refinedReceiverLowerBound);
+ } else {
+ lookupResult =
+ resolutionResult.lookupVirtualDispatchTargets(
+ appView.definitionForProgramType(invocationContext),
+ appView.withLiveness().appInfo());
+ }
+ if (lookupResult.isLookupResultFailure()) {
+ return null;
}
Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
lookupResult.forEach(
methodTarget -> result.add(methodTarget.getMethod()),
lambda -> {
- assert false;
+ // TODO(b/150277553): Support lambda targets.
});
return result;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index f7b8688..41b1124 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -6,7 +6,6 @@
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.InvokePolymorphicRange;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -22,6 +21,7 @@
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Collection;
import java.util.List;
@@ -126,7 +126,7 @@
@Override
public Collection<DexEncodedMethod> lookupTargets(
- AppView<? extends AppInfoWithSubtyping> appView, DexType invocationContext) {
+ AppView<AppInfoWithLiveness> appView, DexType invocationContext) {
// TODO(herhut): Implement lookup target for invokePolymorphic.
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 18b62e6..92b23eb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -218,15 +218,14 @@
resolution.lookupVirtualDispatchTargets(
appView.definitionForProgramType(context), appView.appInfo());
if (lookupResult.isLookupResultSuccess()) {
- // TODO(b/150277553): Add lambda methods to the call graph.
Set<DexEncodedMethod> targets = new HashSet<>();
lookupResult
.asLookupResultSuccess()
.forEach(
methodTarget -> targets.add(methodTarget.getMethod()),
- lambdaTarget -> {
- assert false;
- });
+ lambdaTarget ->
+ // The call target will ultimately be the implementation method.
+ targets.add(lambdaTarget.getImplementationMethod().getMethod()));
return targets;
}
}
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 e29a1c1..1b78ff7 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
@@ -315,11 +315,10 @@
: null;
this.lambdaMerger =
options.enableLambdaMerging ? new LambdaMerger(appViewWithLiveness) : null;
- this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness);
this.enumUnboxer = options.enableEnumUnboxing ? new EnumUnboxer(appViewWithLiveness) : null;
+ this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
this.inliner =
- new Inliner(
- appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter, enumUnboxer);
+ new Inliner(appViewWithLiveness, mainDexClasses, lambdaMerger, lensCodeRewriter);
this.outliner = new Outliner(appViewWithLiveness);
this.memberValuePropagation =
options.enableValuePropagation ? new MemberValuePropagation(appViewWithLiveness) : null;
@@ -1148,10 +1147,6 @@
codeRewriter.simplifyDebugLocals(code);
}
- if (enumUnboxer != null && methodProcessor.isPost()) {
- enumUnboxer.rewriteCode(code);
- }
-
if (appView.graphLense().hasCodeRewritings()) {
assert lensCodeRewriter != null;
timing.begin("Lens rewrite");
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 7daa4a1..0d91bab 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -84,6 +84,7 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
import com.android.tools.r8.logging.Log;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -99,10 +100,12 @@
private final AppView<? extends AppInfoWithSubtyping> appView;
+ private final EnumUnboxer enumUnboxer;
private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
- LensCodeRewriter(AppView<? extends AppInfoWithSubtyping> appView) {
+ LensCodeRewriter(AppView<? extends AppInfoWithSubtyping> appView, EnumUnboxer enumUnboxer) {
this.appView = appView;
+ this.enumUnboxer = enumUnboxer;
}
private Value makeOutValue(Instruction insn, IRCode code) {
@@ -117,11 +120,12 @@
/** Replace type appearances, invoke targets and field accesses with actual definitions. */
public void rewrite(IRCode code, DexEncodedMethod method) {
+ Set<Phi> affectedPhis =
+ enumUnboxer != null ? enumUnboxer.rewriteCode(code) : Sets.newIdentityHashSet();
GraphLense graphLense = appView.graphLense();
DexItemFactory factory = appView.dexItemFactory();
// Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this
// we track all phi's that needs to be re-computed.
- Set<Phi> affectedPhis = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blocks = code.listIterator();
boolean mayHaveUnreachableBlocks = false;
while (blocks.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
index 20ab05c..0365d43 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchConverter.java
@@ -187,7 +187,7 @@
}
Instruction definition = root.definition;
return definition.isInvokeVirtual()
- && definition.asInvokeVirtual().getInvokedMethod() == dexItemFactory.stringMethods.hashCode;
+ && definition.asInvokeVirtual().getInvokedMethod() == dexItemFactory.stringMembers.hashCode;
}
static class StringSwitchBuilderInfo {
@@ -376,7 +376,7 @@
}
if (instruction.isInvokeVirtual()) {
InvokeVirtual invoke = instruction.asInvokeVirtual();
- if (invoke.getInvokedMethod() == dexItemFactory.stringMethods.hashCode
+ if (invoke.getInvokedMethod() == dexItemFactory.stringMembers.hashCode
&& invoke.getReceiver() == stringValue
&& invoke.outValue().onlyUsedInBlock(block)) {
continue;
@@ -503,7 +503,7 @@
InvokeVirtual theInvoke = instructionIterator.next().asInvokeVirtual();
if (theInvoke == null
- || theInvoke.getInvokedMethod() != dexItemFactory.stringMethods.equals
+ || theInvoke.getInvokedMethod() != dexItemFactory.stringMembers.equals
|| theInvoke.getReceiver() != stringValue
|| theInvoke.inValues().get(1) != theString.outValue()) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index b1f53e1..fcfc1e9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -108,7 +108,7 @@
InvokeVirtual invokeInstruction =
new InvokeVirtual(
- appView.dexItemFactory().stringMethods.equals,
+ appView.dexItemFactory().stringMembers.equals,
code.createValue(PrimitiveTypeElement.getInt()),
ImmutableList.of(theSwitch.value(), constStringInstruction.outValue()));
invokeInstruction.setPosition(Position.syntheticNone());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index e9b9b7e..28ac0fd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -361,38 +361,46 @@
if (clazz == null) {
// Report missing class since we don't know if it is an interface.
warnMissingType(encodedMethod.method, method.holder);
-
} else if (clazz.isInterface()) {
if (clazz.isLibraryClass()) {
throw new CompilationError("Unexpected call to a private method " +
"defined in library class " + clazz.toSourceString(),
getMethodOrigin(encodedMethod.method));
}
-
- // This might be either private method call, or a call to default
- // interface method made via invoke-direct.
- DexEncodedMethod virtualTarget = null;
- for (DexEncodedMethod candidate : clazz.virtualMethods()) {
- if (candidate.method == method) {
- virtualTarget = candidate;
- break;
- }
- }
-
- if (virtualTarget != null) {
- // This is a invoke-direct call to a virtual method.
- instructions.replaceCurrentInstruction(
- new InvokeStatic(defaultAsMethodOfCompanionClass(method),
- invokeDirect.outValue(), invokeDirect.arguments()));
-
- } else {
- // Otherwise this must be a private instance method call. Note that the referenced
+ DexEncodedMethod directTarget = appView.definitionFor(method);
+ if (directTarget != null) {
+ // This can be a private instance method call. Note that the referenced
// method is expected to be in the current class since it is private, but desugaring
// may move some methods or their code into other classes.
-
- instructions.replaceCurrentInstruction(
- new InvokeStatic(privateAsMethodOfCompanionClass(method),
- invokeDirect.outValue(), invokeDirect.arguments()));
+ if (directTarget.isPrivateMethod()) {
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ privateAsMethodOfCompanionClass(method),
+ invokeDirect.outValue(),
+ invokeDirect.arguments()));
+ } else {
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ defaultAsMethodOfCompanionClass(method),
+ invokeDirect.outValue(),
+ invokeDirect.arguments()));
+ }
+ } else {
+ // The method can be a default method in the interface hierarchy.
+ DexClassAndMethod virtualTarget =
+ appView.appInfo().lookupMaximallySpecificMethod(clazz, method);
+ if (virtualTarget != null) {
+ // This is a invoke-direct call to a virtual method.
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ defaultAsMethodOfCompanionClass(virtualTarget.getMethod().method),
+ invokeDirect.outValue(),
+ invokeDirect.arguments()));
+ } else {
+ // The below assert is here because a well-type program should have a target, but we
+ // cannot throw a compilation error, since we have no knowledge about the input.
+ assert false;
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index d6d698e..85b98ba 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -378,4 +378,8 @@
assert !instructions.hasNext();
nextBlock.copyCatchHandlers(code, blocks, currentBlock, getAppView().options());
}
+
+ public Map<DexType, LambdaClass> getKnownLambdaClasses() {
+ return knownLambdaClasses;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
index 74b1631..f0b74ea 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ServiceLoaderSourceCode.java
@@ -70,7 +70,7 @@
}
builder.add(
- new CfInvoke(INVOKESTATIC, factory.utilArraysMethods.asList, false),
+ new CfInvoke(INVOKESTATIC, factory.javaUtilArraysMethods.asList, false),
new CfInvoke(
INVOKEINTERFACE,
factory.createMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
index b43abaa..342c767 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/NumericMethodRewrites.java
@@ -42,8 +42,11 @@
Set<Value> affectedValues) {
List<Value> values = invoke.inValues();
assert values.size() == 1;
- invoke.outValue().replaceUsers(values.get(0));
- iterator.remove();
+ if (invoke.hasOutValue()) {
+ invoke.outValue().replaceUsers(values.get(0));
+ }
+ // TODO(b/152853271): Debugging information is lost here (DebugLocalWrite may be required).
+ iterator.removeOrReplaceByDebugLocalRead();
}
private NumericMethodRewrites() {
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 fee1f02..9f34126 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
@@ -1821,6 +1821,15 @@
if (!next.isConstInstruction()) {
continue;
}
+
+ // We don't want to push const string instructions down into code that has monitors since
+ // we may attach catch handlers that are not catch-all when inlining. This is symmetric in how
+ // we don't do const string canonicalization.
+ if ((next.isConstString() || next.isDexItemBasedConstString())
+ && code.metadata().mayHaveMonitorInstruction()) {
+ continue;
+ }
+
ConstInstruction instruction = next.asConstInstruction();
if (!selector.test(instruction) || instruction.outValue().hasLocalInfo()) {
continue;
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 33d982a..55f42ab 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
@@ -45,7 +45,6 @@
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostOptimization;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.inliner.DefaultInliningReasonStrategy;
@@ -80,7 +79,6 @@
private final Set<DexMethod> blacklist;
private final LambdaMerger lambdaMerger;
private final LensCodeRewriter lensCodeRewriter;
- private final EnumUnboxer enumUnboxer;
final MainDexClasses mainDexClasses;
// State for inlining methods which are known to be called twice.
@@ -93,8 +91,7 @@
AppView<AppInfoWithLiveness> appView,
MainDexClasses mainDexClasses,
LambdaMerger lambdaMerger,
- LensCodeRewriter lensCodeRewriter,
- EnumUnboxer enumUnboxer) {
+ LensCodeRewriter lensCodeRewriter) {
Kotlin.Intrinsics intrinsics = appView.dexItemFactory().kotlin.intrinsics;
this.appView = appView;
this.blacklist =
@@ -103,7 +100,6 @@
: ImmutableSet.of(intrinsics.throwNpe, intrinsics.throwParameterIsNullException);
this.lambdaMerger = lambdaMerger;
this.lensCodeRewriter = lensCodeRewriter;
- this.enumUnboxer = enumUnboxer;
this.mainDexClasses = mainDexClasses;
}
@@ -599,8 +595,7 @@
DexEncodedMethod context,
InliningIRProvider inliningIRProvider,
LambdaMerger lambdaMerger,
- LensCodeRewriter lensCodeRewriter,
- EnumUnboxer enumUnboxer) {
+ LensCodeRewriter lensCodeRewriter) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
InternalOptions options = appView.options();
@@ -738,9 +733,6 @@
if (inliningIRProvider.shouldApplyCodeRewritings(code.method)) {
assert lensCodeRewriter != null;
- if (enumUnboxer != null) {
- enumUnboxer.rewriteCode(code);
- }
lensCodeRewriter.rewrite(code, target);
}
if (lambdaMerger != null) {
@@ -1000,13 +992,7 @@
InlineeWithReason inlinee =
action.buildInliningIR(
- appView,
- invoke,
- context,
- inliningIRProvider,
- lambdaMerger,
- lensCodeRewriter,
- enumUnboxer);
+ appView, invoke, context, inliningIRProvider, lambdaMerger, lensCodeRewriter);
if (strategy.willExceedBudget(
code, invoke, inlinee, block, whyAreYouNotInliningReporter)) {
assert whyAreYouNotInliningReporter.unsetReasonHasBeenReportedFlag();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 2e41703..bc294fa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -69,10 +69,9 @@
}
// Add non-null after
// 1) instructions that implicitly indicate receiver/array is not null.
- // 2) invocations that call non-overridable library methods that are known to return non null.
- // 3) invocations that are guaranteed to return a non-null value.
- // 4) parameters that are not null after the invocation.
- // 5) field-get instructions that are guaranteed to read a non-null value.
+ // 2) invocations that are guaranteed to return a non-null value.
+ // 3) parameters that are not null after the invocation.
+ // 4) field-get instructions that are guaranteed to read a non-null value.
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction current = iterator.next();
@@ -90,26 +89,18 @@
InvokeMethod invoke = current.asInvokeMethod();
DexMethod invokedMethod = invoke.getInvokedMethod();
- // Case (2), invocations that call non-overridable library methods that are known to
- // return non null.
- if (dexItemFactory.libraryMethodsReturningNonNull.contains(invokedMethod)) {
- if (current.hasOutValue() && isNullableReferenceTypeWithUsers(outValue)) {
- knownToBeNonNullValues.add(outValue);
- }
- }
-
DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
if (singleTarget != null) {
MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
- // Case (3), invocations that are guaranteed to return a non-null value.
+ // Case (2), invocations that are guaranteed to return a non-null value.
if (optimizationInfo.neverReturnsNull()) {
if (invoke.hasOutValue() && isNullableReferenceTypeWithUsers(outValue)) {
knownToBeNonNullValues.add(outValue);
}
}
- // Case (4), parameters that are not null after the invocation.
+ // Case (3), parameters that are not null after the invocation.
BitSet nonNullParamOnNormalExits = optimizationInfo.getNonNullParamOnNormalExits();
if (nonNullParamOnNormalExits != null) {
for (int i = 0; i < current.inValues().size(); i++) {
@@ -123,7 +114,7 @@
}
}
} else if (current.isFieldGet()) {
- // Case (5), field-get instructions that are guaranteed to read a non-null value.
+ // Case (4), field-get instructions that are guaranteed to read a non-null value.
FieldInstruction fieldInstruction = current.asFieldInstruction();
DexField field = fieldInstruction.getField();
if (field.type.isReferenceType() && isNullableReferenceTypeWithUsers(outValue)) {
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 81b32e8..4b50b6e 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
@@ -16,7 +16,6 @@
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.value.SingleValue;
import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InitClass;
@@ -33,6 +32,8 @@
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
@@ -43,7 +44,7 @@
/**
* Eliminate redundant field loads.
*
- * <p>Simple algorithm that goes through all blocks in one pass in dominator order and propagates
+ * <p>Simple algorithm that goes through all blocks in one pass in topological order and propagates
* active field sets across control-flow edges where the target has only one predecessor.
*/
// TODO(ager): Evaluate speed/size for computing active field sets in a fixed-point computation.
@@ -52,7 +53,6 @@
private final AppView<?> appView;
private final DexEncodedMethod method;
private final IRCode code;
- private final DominatorTree dominatorTree;
// Values that may require type propagation.
private final Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -68,7 +68,6 @@
this.appView = appView;
this.method = code.method;
this.code = code;
- dominatorTree = new DominatorTree(code);
}
public static boolean shouldRun(AppView<?> appView, IRCode code) {
@@ -140,6 +139,13 @@
}
}
+ public boolean isFinal(DexEncodedField field) {
+ if (field.isProgramField(appView)) {
+ return field.isFinal();
+ }
+ return appView.libraryMethodOptimizer().isFinalLibraryField(field);
+ }
+
private DexEncodedField resolveField(DexField field) {
if (appView.enableWholeProgramOptimizations()) {
return appView.appInfo().resolveField(field);
@@ -152,138 +158,169 @@
public void run() {
DexType context = method.holder();
- for (BasicBlock block : dominatorTree.getSortedBlocks()) {
- computeActiveStateOnBlockEntry(block);
- InstructionListIterator it = block.listIterator(code);
- while (it.hasNext()) {
- Instruction instruction = it.next();
- if (instruction.isFieldInstruction()) {
- DexField field = instruction.asFieldInstruction().getField();
- DexEncodedField definition = resolveField(field);
- if (definition == null || definition.isVolatile()) {
- killAllNonFinalActiveFields();
- continue;
- }
-
- if (instruction.isInstanceGet()) {
- InstanceGet instanceGet = instruction.asInstanceGet();
- if (instanceGet.outValue().hasLocalInfo()) {
- continue;
- }
- Value object = instanceGet.object().getAliasedValue();
- FieldAndObject fieldAndObject = new FieldAndObject(field, object);
- FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
- if (replacement != null) {
- replacement.eliminateRedundantRead(it, instanceGet);
- } else {
- activeState.putNonFinalInstanceField(
- fieldAndObject, new ExistingValue(instanceGet.value()));
- }
- } else if (instruction.isInstancePut()) {
- InstancePut instancePut = instruction.asInstancePut();
- // An instance-put instruction can potentially write the given field on all objects
- // because of aliases.
- killNonFinalActiveFields(instancePut);
- // ... but at least we know the field value for this particular object.
- Value object = instancePut.object().getAliasedValue();
- FieldAndObject fieldAndObject = new FieldAndObject(field, object);
- ExistingValue value = new ExistingValue(instancePut.value());
- if (definition.isFinal()) {
- assert method.isInstanceInitializer() || verifyWasInstanceInitializer();
- activeState.putFinalInstanceField(fieldAndObject, value);
- } else {
- activeState.putNonFinalInstanceField(fieldAndObject, value);
- }
- } else if (instruction.isStaticGet()) {
- StaticGet staticGet = instruction.asStaticGet();
- if (staticGet.outValue().hasLocalInfo()) {
- continue;
- }
- FieldValue replacement = activeState.getStaticFieldValue(field);
- if (replacement != null) {
- replacement.eliminateRedundantRead(it, staticGet);
- } else {
- // A field get on a different class can cause <clinit> to run and change static
- // field values.
- killNonFinalActiveFields(staticGet);
- activeState.putNonFinalStaticField(field, new ExistingValue(staticGet.value()));
- }
- } else if (instruction.isStaticPut()) {
- StaticPut staticPut = instruction.asStaticPut();
- // A field put on a different class can cause <clinit> to run and change static
- // field values.
- killNonFinalActiveFields(staticPut);
- ExistingValue value = new ExistingValue(staticPut.value());
- if (definition.isFinal()) {
- assert method.isClassInitializer();
- activeState.putFinalStaticField(field, value);
- } else {
- activeState.putNonFinalStaticField(field, value);
- }
- }
- } else if (instruction.isInitClass()) {
- InitClass initClass = instruction.asInitClass();
- assert !initClass.outValue().hasAnyUsers();
- DexType clazz = initClass.getClassValue();
- if (activeState.isClassInitialized(clazz)) {
- it.removeOrReplaceByDebugLocalRead();
- }
- activeState.markClassAsInitialized(clazz);
- } else if (instruction.isMonitor()) {
- if (instruction.asMonitor().isEnter()) {
- killAllNonFinalActiveFields();
- }
- } else if (instruction.isInvokeDirect()) {
- handleInvokeDirect(instruction.asInvokeDirect());
- } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
- killAllNonFinalActiveFields();
- } else if (instruction.isNewInstance()) {
- NewInstance newInstance = instruction.asNewInstance();
- if (newInstance.clazz.classInitializationMayHaveSideEffects(
- appView,
- // Types that are a super type of `context` are guaranteed to be initialized already.
- type -> appView.isSubtype(context, type).isTrue(),
- Sets.newIdentityHashSet())) {
- killAllNonFinalActiveFields();
- }
- } else {
- // If the current instruction could trigger a method invocation, it could also cause field
- // values to change. In that case, it must be handled above.
- assert !instruction.instructionMayTriggerMethodInvocation(appView, context);
-
- // If this assertion fails for a new instruction we need to determine if that instruction
- // has side-effects that can change the value of fields. If so, it must be handled above.
- // If not, it can be safely added to the assert.
- assert instruction.isArgument()
- || instruction.isArrayGet()
- || instruction.isArrayLength()
- || instruction.isArrayPut()
- || instruction.isAssume()
- || instruction.isBinop()
- || instruction.isCheckCast()
- || instruction.isConstClass()
- || instruction.isConstMethodHandle()
- || instruction.isConstMethodType()
- || instruction.isConstNumber()
- || instruction.isConstString()
- || instruction.isDebugInstruction()
- || instruction.isDexItemBasedConstString()
- || instruction.isGoto()
- || instruction.isIf()
- || instruction.isInstanceOf()
- || instruction.isInvokeMultiNewArray()
- || instruction.isInvokeNewArray()
- || instruction.isMoveException()
- || instruction.isNewArrayEmpty()
- || instruction.isNewArrayFilledData()
- || instruction.isReturn()
- || instruction.isSwitch()
- || instruction.isThrow()
- || instruction.isUnop()
- : "Unexpected instruction of type " + instruction.getClass().getTypeName();
- }
+ Reference2IntMap<BasicBlock> pendingNormalSuccessors = new Reference2IntOpenHashMap<>();
+ for (BasicBlock block : code.blocks) {
+ if (!block.hasUniqueSuccessor()) {
+ pendingNormalSuccessors.put(block, block.numberOfNormalSuccessors());
}
- recordActiveStateOnBlockExit(block);
+ }
+
+ Set<BasicBlock> visited = Sets.newIdentityHashSet();
+ for (BasicBlock head : code.topologicallySortedBlocks()) {
+ if (!visited.add(head)) {
+ continue;
+ }
+ computeActiveStateOnBlockEntry(head);
+ removeDeadBlockExitStates(head, pendingNormalSuccessors);
+ BasicBlock block = head;
+ BasicBlock end = null;
+ do {
+ InstructionListIterator it = block.listIterator(code);
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isFieldInstruction()) {
+ DexField field = instruction.asFieldInstruction().getField();
+ DexEncodedField definition = resolveField(field);
+ if (definition == null || definition.isVolatile()) {
+ killAllNonFinalActiveFields();
+ continue;
+ }
+
+ if (instruction.isInstanceGet()) {
+ InstanceGet instanceGet = instruction.asInstanceGet();
+ if (instanceGet.outValue().hasLocalInfo()) {
+ continue;
+ }
+ Value object = instanceGet.object().getAliasedValue();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
+ if (replacement != null) {
+ replacement.eliminateRedundantRead(it, instanceGet);
+ } else {
+ activeState.putNonFinalInstanceField(
+ fieldAndObject, new ExistingValue(instanceGet.value()));
+ }
+ } else if (instruction.isInstancePut()) {
+ InstancePut instancePut = instruction.asInstancePut();
+ // An instance-put instruction can potentially write the given field on all objects
+ // because of aliases.
+ killNonFinalActiveFields(instancePut);
+ // ... but at least we know the field value for this particular object.
+ Value object = instancePut.object().getAliasedValue();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ ExistingValue value = new ExistingValue(instancePut.value());
+ if (isFinal(definition)) {
+ assert method.isInstanceInitializer() || verifyWasInstanceInitializer();
+ activeState.putFinalInstanceField(fieldAndObject, value);
+ } else {
+ activeState.putNonFinalInstanceField(fieldAndObject, value);
+ }
+ } else if (instruction.isStaticGet()) {
+ StaticGet staticGet = instruction.asStaticGet();
+ if (staticGet.outValue().hasLocalInfo()) {
+ continue;
+ }
+ FieldValue replacement = activeState.getStaticFieldValue(field);
+ if (replacement != null) {
+ replacement.eliminateRedundantRead(it, staticGet);
+ } else {
+ // A field get on a different class can cause <clinit> to run and change static
+ // field values.
+ killNonFinalActiveFields(staticGet);
+ FieldValue value = new ExistingValue(staticGet.value());
+ if (isFinal(definition)) {
+ activeState.putFinalStaticField(field, value);
+ } else {
+ activeState.putNonFinalStaticField(field, value);
+ }
+ }
+ } else if (instruction.isStaticPut()) {
+ StaticPut staticPut = instruction.asStaticPut();
+ // A field put on a different class can cause <clinit> to run and change static
+ // field values.
+ killNonFinalActiveFields(staticPut);
+ ExistingValue value = new ExistingValue(staticPut.value());
+ if (definition.isFinal()) {
+ assert method.isClassInitializer();
+ activeState.putFinalStaticField(field, value);
+ } else {
+ activeState.putNonFinalStaticField(field, value);
+ }
+ }
+ } else if (instruction.isInitClass()) {
+ InitClass initClass = instruction.asInitClass();
+ assert !initClass.outValue().hasAnyUsers();
+ DexType clazz = initClass.getClassValue();
+ if (activeState.isClassInitialized(clazz)) {
+ it.removeOrReplaceByDebugLocalRead();
+ }
+ activeState.markClassAsInitialized(clazz);
+ } else if (instruction.isMonitor()) {
+ if (instruction.asMonitor().isEnter()) {
+ killAllNonFinalActiveFields();
+ }
+ } else if (instruction.isInvokeDirect()) {
+ handleInvokeDirect(instruction.asInvokeDirect());
+ } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
+ killAllNonFinalActiveFields();
+ } else if (instruction.isNewInstance()) {
+ NewInstance newInstance = instruction.asNewInstance();
+ if (newInstance.clazz.classInitializationMayHaveSideEffects(
+ appView,
+ // Types that are a super type of `context` are guaranteed to be initialized
+ // already.
+ type -> appView.isSubtype(context, type).isTrue(),
+ Sets.newIdentityHashSet())) {
+ killAllNonFinalActiveFields();
+ }
+ } else {
+ // If the current instruction could trigger a method invocation, it could also cause
+ // field values to change. In that case, it must be handled above.
+ assert !instruction.instructionMayTriggerMethodInvocation(appView, context);
+
+ // If this assertion fails for a new instruction we need to determine if that
+ // instruction has side-effects that can change the value of fields. If so, it must be
+ // handled above. If not, it can be safely added to the assert.
+ assert instruction.isArgument()
+ || instruction.isArrayGet()
+ || instruction.isArrayLength()
+ || instruction.isArrayPut()
+ || instruction.isAssume()
+ || instruction.isBinop()
+ || instruction.isCheckCast()
+ || instruction.isConstClass()
+ || instruction.isConstMethodHandle()
+ || instruction.isConstMethodType()
+ || instruction.isConstNumber()
+ || instruction.isConstString()
+ || instruction.isDebugInstruction()
+ || instruction.isDexItemBasedConstString()
+ || instruction.isGoto()
+ || instruction.isIf()
+ || instruction.isInstanceOf()
+ || instruction.isInvokeMultiNewArray()
+ || instruction.isInvokeNewArray()
+ || instruction.isMoveException()
+ || instruction.isNewArrayEmpty()
+ || instruction.isNewArrayFilledData()
+ || instruction.isReturn()
+ || instruction.isSwitch()
+ || instruction.isThrow()
+ || instruction.isUnop()
+ : "Unexpected instruction of type " + instruction.getClass().getTypeName();
+ }
+ }
+ if (block.hasUniqueNormalSuccessorWithUniquePredecessor()) {
+ block = block.getUniqueNormalSuccessor();
+ assert !visited.contains(block);
+ visited.add(block);
+ } else {
+ end = block;
+ block = null;
+ }
+ } while (block != null);
+ assert end != null;
+ recordActiveStateOnBlockExit(end);
}
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
@@ -382,9 +419,29 @@
activeState = state;
}
+ private void removeDeadBlockExitStates(
+ BasicBlock current, Reference2IntMap<BasicBlock> pendingNormalSuccessorsMap) {
+ for (BasicBlock predecessor : current.getPredecessors()) {
+ if (predecessor.hasUniqueSuccessor()) {
+ activeStateAtExit.remove(predecessor);
+ } else {
+ if (predecessor.hasNormalSuccessor(current)) {
+ int pendingNormalSuccessors = pendingNormalSuccessorsMap.getInt(predecessor) - 1;
+ if (pendingNormalSuccessors == 0) {
+ activeStateAtExit.remove(predecessor);
+ } else {
+ pendingNormalSuccessorsMap.put(predecessor, pendingNormalSuccessors);
+ }
+ }
+ }
+ }
+ }
+
private void recordActiveStateOnBlockExit(BasicBlock block) {
assert !activeStateAtExit.containsKey(block);
- activeStateAtExit.put(block, activeState);
+ if (!activeState.isEmpty()) {
+ activeStateAtExit.put(block, activeState);
+ }
}
private void killAllNonFinalActiveFields() {
@@ -419,50 +476,94 @@
static class State {
- private final Map<FieldAndObject, FieldValue> finalInstanceFieldValues = new HashMap<>();
+ private Map<FieldAndObject, FieldValue> finalInstanceFieldValues;
- private final Map<DexField, FieldValue> finalStaticFieldValues = new IdentityHashMap<>();
+ private Map<DexField, FieldValue> finalStaticFieldValues;
- private final Set<DexType> initializedClasses = Sets.newIdentityHashSet();
+ private Set<DexType> initializedClasses;
- private final Map<FieldAndObject, FieldValue> nonFinalInstanceFieldValues = new HashMap<>();
+ private Map<FieldAndObject, FieldValue> nonFinalInstanceFieldValues;
- private final Map<DexField, FieldValue> nonFinalStaticFieldValues = new IdentityHashMap<>();
+ private Map<DexField, FieldValue> nonFinalStaticFieldValues;
public State() {}
public State(State state) {
- finalInstanceFieldValues.putAll(state.finalInstanceFieldValues);
- finalStaticFieldValues.putAll(state.finalStaticFieldValues);
- initializedClasses.addAll(state.initializedClasses);
- nonFinalInstanceFieldValues.putAll(state.nonFinalInstanceFieldValues);
- nonFinalStaticFieldValues.putAll(state.nonFinalStaticFieldValues);
+ if (state.finalInstanceFieldValues != null && !state.finalInstanceFieldValues.isEmpty()) {
+ finalInstanceFieldValues = new HashMap<>();
+ finalInstanceFieldValues.putAll(state.finalInstanceFieldValues);
+ }
+ if (state.finalStaticFieldValues != null && !state.finalStaticFieldValues.isEmpty()) {
+ finalStaticFieldValues = new IdentityHashMap<>();
+ finalStaticFieldValues.putAll(state.finalStaticFieldValues);
+ }
+ if (state.initializedClasses != null && !state.initializedClasses.isEmpty()) {
+ initializedClasses = Sets.newIdentityHashSet();
+ initializedClasses.addAll(state.initializedClasses);
+ }
+ if (state.nonFinalInstanceFieldValues != null
+ && !state.nonFinalInstanceFieldValues.isEmpty()) {
+ nonFinalInstanceFieldValues = new HashMap<>();
+ nonFinalInstanceFieldValues.putAll(state.nonFinalInstanceFieldValues);
+ }
+ if (state.nonFinalStaticFieldValues != null && !state.nonFinalStaticFieldValues.isEmpty()) {
+ nonFinalStaticFieldValues = new IdentityHashMap<>();
+ nonFinalStaticFieldValues.putAll(state.nonFinalStaticFieldValues);
+ }
}
public void clearNonFinalInstanceFields() {
- nonFinalInstanceFieldValues.clear();
+ nonFinalInstanceFieldValues = null;
}
public void clearNonFinalStaticFields() {
- nonFinalStaticFieldValues.clear();
+ nonFinalStaticFieldValues = null;
}
public FieldValue getInstanceFieldValue(FieldAndObject field) {
- FieldValue value = nonFinalInstanceFieldValues.get(field);
- return value != null ? value : finalInstanceFieldValues.get(field);
+ FieldValue value =
+ nonFinalInstanceFieldValues != null ? nonFinalInstanceFieldValues.get(field) : null;
+ if (value != null) {
+ return value;
+ }
+ return finalInstanceFieldValues != null ? finalInstanceFieldValues.get(field) : null;
}
public FieldValue getStaticFieldValue(DexField field) {
- FieldValue value = nonFinalStaticFieldValues.get(field);
- return value != null ? value : finalStaticFieldValues.get(field);
+ FieldValue value =
+ nonFinalStaticFieldValues != null ? nonFinalStaticFieldValues.get(field) : null;
+ if (value != null) {
+ return value;
+ }
+ return finalStaticFieldValues != null ? finalStaticFieldValues.get(field) : null;
}
public void intersect(State state) {
- intersectFieldValues(finalInstanceFieldValues, state.finalInstanceFieldValues);
- intersectFieldValues(finalStaticFieldValues, state.finalStaticFieldValues);
- intersectInitializedClasses(initializedClasses, state.initializedClasses);
- intersectFieldValues(nonFinalInstanceFieldValues, state.nonFinalInstanceFieldValues);
- intersectFieldValues(nonFinalStaticFieldValues, state.nonFinalStaticFieldValues);
+ if (finalInstanceFieldValues != null && state.finalInstanceFieldValues != null) {
+ intersectFieldValues(finalInstanceFieldValues, state.finalInstanceFieldValues);
+ } else {
+ finalInstanceFieldValues = null;
+ }
+ if (finalStaticFieldValues != null && state.finalStaticFieldValues != null) {
+ intersectFieldValues(finalStaticFieldValues, state.finalStaticFieldValues);
+ } else {
+ finalStaticFieldValues = null;
+ }
+ if (initializedClasses != null && state.initializedClasses != null) {
+ intersectInitializedClasses(initializedClasses, state.initializedClasses);
+ } else {
+ initializedClasses = null;
+ }
+ if (nonFinalInstanceFieldValues != null && state.nonFinalInstanceFieldValues != null) {
+ intersectFieldValues(nonFinalInstanceFieldValues, state.nonFinalInstanceFieldValues);
+ } else {
+ nonFinalInstanceFieldValues = null;
+ }
+ if (nonFinalStaticFieldValues != null && state.nonFinalStaticFieldValues != null) {
+ intersectFieldValues(nonFinalStaticFieldValues, state.nonFinalStaticFieldValues);
+ } else {
+ nonFinalStaticFieldValues = null;
+ }
}
private static <K> void intersectFieldValues(
@@ -476,7 +577,23 @@
}
public boolean isClassInitialized(DexType clazz) {
- return initializedClasses.contains(clazz);
+ return initializedClasses != null && initializedClasses.contains(clazz);
+ }
+
+ public boolean isEmpty() {
+ return isEmpty(finalInstanceFieldValues)
+ && isEmpty(finalStaticFieldValues)
+ && isEmpty(initializedClasses)
+ && isEmpty(nonFinalInstanceFieldValues)
+ && isEmpty(nonFinalStaticFieldValues);
+ }
+
+ private static boolean isEmpty(Set<?> set) {
+ return set == null || set.isEmpty();
+ }
+
+ private static boolean isEmpty(Map<?, ?> map) {
+ return map == null || map.isEmpty();
}
// If a field get instruction throws an exception it did not have an effect on the value of the
@@ -487,17 +604,22 @@
if (instruction.isInstanceGet()) {
Value object = instruction.asInstanceGet().object().getAliasedValue();
FieldAndObject fieldAndObject = new FieldAndObject(field, object);
- removeNonFinalInstanceField(fieldAndObject);
+ removeInstanceField(fieldAndObject);
} else if (instruction.isStaticGet()) {
- removeNonFinalStaticField(field);
+ removeStaticField(field);
}
}
private void killActiveInitializedClassesForExceptionalExit(InitClass instruction) {
- initializedClasses.remove(instruction.getClassValue());
+ if (initializedClasses != null) {
+ initializedClasses.remove(instruction.getClassValue());
+ }
}
public void markClassAsInitialized(DexType clazz) {
+ if (initializedClasses == null) {
+ initializedClasses = Sets.newIdentityHashSet();
+ }
initializedClasses.add(clazz);
}
@@ -507,15 +629,21 @@
}
public void removeFinalInstanceField(FieldAndObject field) {
- finalInstanceFieldValues.remove(field);
+ if (finalInstanceFieldValues != null) {
+ finalInstanceFieldValues.remove(field);
+ }
}
public void removeNonFinalInstanceField(FieldAndObject field) {
- nonFinalInstanceFieldValues.remove(field);
+ if (nonFinalInstanceFieldValues != null) {
+ nonFinalInstanceFieldValues.remove(field);
+ }
}
public void removeNonFinalInstanceFields(DexField field) {
- nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field == field);
+ if (nonFinalInstanceFieldValues != null) {
+ nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field == field);
+ }
}
public void removeStaticField(DexField field) {
@@ -524,28 +652,44 @@
}
public void removeFinalStaticField(DexField field) {
- finalStaticFieldValues.remove(field);
+ if (finalStaticFieldValues != null) {
+ finalStaticFieldValues.remove(field);
+ }
}
public void removeNonFinalStaticField(DexField field) {
- nonFinalStaticFieldValues.remove(field);
+ if (nonFinalStaticFieldValues != null) {
+ nonFinalStaticFieldValues.remove(field);
+ }
}
public void putFinalInstanceField(FieldAndObject field, FieldValue value) {
+ if (finalInstanceFieldValues == null) {
+ finalInstanceFieldValues = new HashMap<>();
+ }
finalInstanceFieldValues.put(field, value);
}
public void putFinalStaticField(DexField field, FieldValue value) {
+ if (finalStaticFieldValues == null) {
+ finalStaticFieldValues = new IdentityHashMap<>();
+ }
finalStaticFieldValues.put(field, value);
}
public void putNonFinalInstanceField(FieldAndObject field, FieldValue value) {
- assert !finalInstanceFieldValues.containsKey(field);
+ assert finalInstanceFieldValues == null || !finalInstanceFieldValues.containsKey(field);
+ if (nonFinalInstanceFieldValues == null) {
+ nonFinalInstanceFieldValues = new HashMap<>();
+ }
nonFinalInstanceFieldValues.put(field, value);
}
public void putNonFinalStaticField(DexField field, FieldValue value) {
- assert !nonFinalStaticFieldValues.containsKey(field);
+ assert nonFinalStaticFieldValues == null || !nonFinalStaticFieldValues.containsKey(field);
+ if (nonFinalStaticFieldValues == null) {
+ nonFinalStaticFieldValues = new IdentityHashMap<>();
+ }
nonFinalStaticFieldValues.put(field, value);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 1e19dea..eef169e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -35,6 +35,9 @@
// Rewrite forName() to const-class if the type is resolvable, accessible and already initialized.
public static void rewriteGetClassOrForNameToConstClass(
AppView<AppInfoWithLiveness> appView, IRCode code) {
+ if (!appView.appInfo().canUseConstClassInstructions(appView.options())) {
+ return;
+ }
Set<Value> affectedValues = Sets.newIdentityHashSet();
DexType context = code.method.holder();
ClassInitializationAnalysis classInitializationAnalysis =
@@ -64,6 +67,10 @@
Value value = code.createValue(typeLattice, current.getLocalInfo());
ConstClass constClass = new ConstClass(value, type);
it.replaceCurrentInstruction(constClass);
+ if (appView.options().isGeneratingClassFiles()) {
+ code.method.upgradeClassFileVersion(
+ appView.options().requiredCfVersionForConstClassInstructions());
+ }
}
}
}
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 ea9141f..6a91188 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
@@ -93,19 +93,6 @@
enumsUnboxingCandidates = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
}
- public void analyzeEnums(IRCode code) {
- // Enum <clinit> and <init> are analyzed in between the two processing phases using optimization
- // feedback. Methods valueOf and values are generated by javac and are analyzed differently.
- DexClass dexClass = appView.definitionFor(code.method.holder());
- if (dexClass.isEnum()
- && (code.method.isInitializer()
- || appView.dexItemFactory().enumMethods.isValueOfMethod(code.method.method, dexClass)
- || appView.dexItemFactory().enumMethods.isValuesMethod(code.method.method, dexClass))) {
- return;
- }
- analyzeEnumsInMethod(code);
- }
-
private void markEnumAsUnboxable(Reason reason, DexProgramClass enumClass) {
assert enumClass.isEnum();
reportFailure(enumClass.type, reason);
@@ -118,29 +105,29 @@
return getEnumUnboxingCandidateOrNull(classType);
}
if (lattice.isArrayType()) {
- ArrayTypeElement arrayLattice = lattice.asArrayType();
- if (arrayLattice.getBaseType().isClassType()) {
- DexType classType = arrayLattice.getBaseType().asClassType().getClassType();
- return getEnumUnboxingCandidateOrNull(classType);
+ ArrayTypeElement arrayType = lattice.asArrayType();
+ if (arrayType.getBaseType().isClassType()) {
+ return getEnumUnboxingCandidateOrNull(arrayType.getBaseType());
}
}
return null;
}
- private DexProgramClass getEnumUnboxingCandidateOrNull(DexType anyType) {
- if (!enumsUnboxingCandidates.containsKey(anyType)) {
+ private DexProgramClass getEnumUnboxingCandidateOrNull(DexType type) {
+ if (!enumsUnboxingCandidates.containsKey(type)) {
return null;
}
- return appView.definitionForProgramType(anyType);
+ return appView.definitionForProgramType(type);
}
- private void analyzeEnumsInMethod(IRCode code) {
+ public void analyzeEnums(IRCode code) {
Set<DexType> eligibleEnums = Sets.newIdentityHashSet();
for (BasicBlock block : code.blocks) {
for (Instruction instruction : block.getInstructions()) {
Value outValue = instruction.outValue();
if (outValue != null) {
- DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(outValue.getType());
+ DexProgramClass enumClass =
+ getEnumUnboxingCandidateOrNull(outValue.getDynamicUpperBoundType(appView));
if (enumClass != null) {
Reason reason = validateEnumUsages(code, outValue, enumClass);
if (reason == Reason.ELIGIBLE) {
@@ -152,9 +139,9 @@
}
}
if (instruction.isConstClass()) {
- analyzeConstClass(instruction.asConstClass());
+ analyzeConstClass(instruction.asConstClass(), eligibleEnums);
} else if (instruction.isCheckCast()) {
- analyzeCheckCast(instruction.asCheckCast());
+ analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
} else if (instruction.isInvokeStatic()) {
// TODO(b/150370354): Since we temporary allow enum unboxing on enums with values and
// valueOf static methods only if such methods are unused, such methods cannot be
@@ -198,24 +185,30 @@
}
}
- private void analyzeCheckCast(CheckCast checkCast) {
+ private void analyzeCheckCast(CheckCast checkCast, Set<DexType> eligibleEnums) {
// We are doing a type check, which typically means the in-value is of an upper
// type and cannot be dealt with.
// If the cast is on a dynamically typed object, the checkCast can be simply removed.
// This allows enum array clone and valueOf to work correctly.
- TypeElement objectType = checkCast.object().getDynamicUpperBoundType(appView);
- if (objectType.equalUpToNullability(
- TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView))) {
- return;
- }
DexProgramClass enumClass =
getEnumUnboxingCandidateOrNull(checkCast.getType().toBaseType(factory));
- if (enumClass != null) {
- markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
+ if (enumClass == null) {
+ return;
}
+ if (allowCheckCast(checkCast)) {
+ eligibleEnums.add(enumClass.type);
+ return;
+ }
+ markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
}
- private void analyzeConstClass(ConstClass constClass) {
+ private boolean allowCheckCast(CheckCast checkCast) {
+ TypeElement objectType = checkCast.object().getDynamicUpperBoundType(appView);
+ return objectType.equalUpToNullability(
+ TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView));
+ }
+
+ private void analyzeConstClass(ConstClass constClass, Set<DexType> eligibleEnums) {
// We are using the ConstClass of an enum, which typically means the enum cannot be unboxed.
// We however allow unboxing if the ConstClass is only used as an argument to Enum#valueOf, to
// allow unboxing of: MyEnum a = Enum.valueOf(MyEnum.class, "A");.
@@ -223,6 +216,7 @@
return;
}
if (constClass.outValue() == null) {
+ eligibleEnums.add(constClass.getValue());
return;
}
if (constClass.outValue().hasPhiUsers()) {
@@ -238,6 +232,7 @@
return;
}
}
+ eligibleEnums.add(constClass.getValue());
}
private void addNullDependencies(Set<Instruction> uses, Set<DexType> eligibleEnums) {
@@ -395,6 +390,14 @@
return Reason.INVALID_INVOKE;
}
if (dexClass.isProgramClass()) {
+ if (dexClass.isEnum() && encodedSingleTarget.isInstanceInitializer()) {
+ if (code.method.holder() == dexClass.type && code.method.isClassInitializer()) {
+ // The enum instance initializer is allowed to be called only from the enum clinit.
+ return Reason.ELIGIBLE;
+ } else {
+ return Reason.INVALID_INIT;
+ }
+ }
int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic());
for (int i = 0; i < singleTarget.proto.parameters.size(); i++) {
if (invokeMethod.inValues().get(offset + i) == enumValue) {
@@ -427,6 +430,13 @@
}
if (singleTarget == factory.enumMethods.ordinal) {
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()
+ && code.method.holder() == enumClass.type
+ && isFirstInstructionAfterArguments(invokeMethod, code)) {
+ return Reason.ELIGIBLE;
+ }
}
return Reason.UNSUPPORTED_LIBRARY_CALL;
}
@@ -443,12 +453,9 @@
if (dexClass == null) {
return Reason.INVALID_FIELD_PUT;
}
- if (dexClass.isEnum()) {
- return Reason.FIELD_PUT_ON_ENUM;
- }
// The put value has to be of the field type.
- if (field.field.type != enumClass.type) {
- return Reason.TYPE_MISSMATCH_FIELD_PUT;
+ if (field.field.type.toBaseType(factory) != enumClass.type) {
+ return Reason.TYPE_MISMATCH_FIELD_PUT;
}
return Reason.ELIGIBLE;
}
@@ -474,6 +481,13 @@
return Reason.INVALID_IF_TYPES;
}
+ if (instruction.isCheckCast()) {
+ if (allowCheckCast(instruction.asCheckCast())) {
+ return Reason.ELIGIBLE;
+ }
+ return Reason.DOWN_CAST;
+ }
+
if (instruction.isArrayLength()) {
// MyEnum[] array = ...; array.length; is valid.
return Reason.ELIGIBLE;
@@ -525,6 +539,16 @@
return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
}
+ private boolean isFirstInstructionAfterArguments(InvokeMethod invokeMethod, IRCode code) {
+ BasicBlock basicBlock = code.entryBlock();
+ for (Instruction instruction : basicBlock.getInstructions()) {
+ if (!instruction.isArgument()) {
+ return instruction == invokeMethod;
+ }
+ }
+ return false;
+ }
+
private void reportEnumsAnalysis() {
assert debugLogEnabled;
Reporter reporter = appView.options().reporter;
@@ -552,10 +576,13 @@
}
}
- public void rewriteCode(IRCode code) {
+ public Set<Phi> rewriteCode(IRCode code) {
+ // This has no effect during primary processing since the enumUnboxerRewriter is set
+ // in between primary and post processing.
if (enumUnboxerRewriter != null) {
- enumUnboxerRewriter.rewriteCode(code);
+ return enumUnboxerRewriter.rewriteCode(code);
}
+ return Sets.newIdentityHashSet();
}
public void synthesizeUtilityClass(
@@ -611,8 +638,9 @@
INVALID_FIELD_PUT,
INVALID_ARRAY_PUT,
FIELD_PUT_ON_ENUM,
- TYPE_MISSMATCH_FIELD_PUT,
+ TYPE_MISMATCH_FIELD_PUT,
INVALID_IF_TYPES,
+ DYNAMIC_TYPE,
ENUM_METHOD_CALLED_WITH_NULL_RECEIVER,
OTHER_UNSUPPORTED_INSTRUCTION;
}
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 73eff15..6ba4154 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
@@ -23,7 +23,6 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
-import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.ArrayAccess;
import com.android.tools.r8.ir.code.IRCode;
@@ -85,11 +84,11 @@
return enumsToUnbox;
}
- void rewriteCode(IRCode code) {
+ Set<Phi> rewriteCode(IRCode code) {
// We should not process the enum methods, they will be removed and they may contain invalid
// rewriting rules.
if (enumsToUnbox.isEmpty()) {
- return;
+ return Sets.newIdentityHashSet();
}
assert code.isConsistentSSABeforeTypesAreCorrect();
Set<Phi> affectedPhis = Sets.newIdentityHashSet();
@@ -165,10 +164,8 @@
assert validateArrayAccess(instruction.asArrayAccess());
}
}
- if (!affectedPhis.isEmpty()) {
- new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
- }
assert code.isConsistentSSABeforeTypesAreCorrect();
+ return affectedPhis;
}
private boolean validateArrayAccess(ArrayAccess arrayAccess) {
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 0bf4d8b..302a58b 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
@@ -124,7 +124,7 @@
@Override
public void methodNeverReturnsNull(DexEncodedMethod method) {
- method.getMutableOptimizationInfo().neverReturnsNull();
+ method.getMutableOptimizationInfo().markNeverReturnsNull();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
index fd9d62b..fa0e683 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -53,7 +53,9 @@
}
DexType enumType = invoke.inValues().get(0).getConstInstruction().asConstClass().getValue();
DexProgramClass enumClass = appView.definitionForProgramType(enumType);
- if (enumClass == null || enumClass.superType != appView.dexItemFactory().enumType) {
+ if (enumClass == null
+ || !enumClass.isEnum()
+ || enumClass.superType != appView.dexItemFactory().enumType) {
return;
}
TypeElement dynamicUpperBoundType =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
similarity index 76%
rename from src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
rename to src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index fc687a0..46a1d7c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -5,7 +5,9 @@
package com.android.tools.r8.ir.optimize.library;
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.LibraryMembers;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.code.IRCode;
@@ -21,23 +23,27 @@
import java.util.Map;
import java.util.Set;
-public class LibraryMethodOptimizer implements CodeOptimization {
+public class LibraryMemberOptimizer implements CodeOptimization {
private final AppView<?> appView;
+ /** Library fields that are assumed to be final. */
+ private final Set<DexEncodedField> finalLibraryFields = Sets.newIdentityHashSet();
+
/** The library types that are modeled. */
private final Set<DexType> modeledLibraryTypes = Sets.newIdentityHashSet();
private final Map<DexType, LibraryMethodModelCollection> libraryMethodModelCollections =
new IdentityHashMap<>();
- public LibraryMethodOptimizer(AppView<?> appView) {
+ public LibraryMemberOptimizer(AppView<?> appView) {
this.appView = appView;
register(new BooleanMethodOptimizer(appView));
register(new ObjectMethodOptimizer(appView));
register(new ObjectsMethodOptimizer(appView));
register(new StringMethodOptimizer(appView));
if (appView.appInfo().hasSubtyping() && appView.options().enableDynamicTypeOptimization) {
+ // Subtyping is required to prove the enum class is a subtype of java.lang.Enum.
register(new EnumMethodOptimizer(appView));
}
@@ -45,12 +51,35 @@
register(new LogMethodOptimizer(appView));
}
+ initializeFinalLibraryFields();
+
LibraryOptimizationInfoInitializer libraryOptimizationInfoInitializer =
new LibraryOptimizationInfoInitializer(appView);
libraryOptimizationInfoInitializer.run();
modeledLibraryTypes.addAll(libraryOptimizationInfoInitializer.getModeledLibraryTypes());
}
+ private void initializeFinalLibraryFields() {
+ for (LibraryMembers libraryMembers : appView.dexItemFactory().libraryMembersCollection) {
+ libraryMembers.forEachFinalField(
+ field -> {
+ DexEncodedField definition = appView.definitionFor(field);
+ if (definition != null) {
+ if (definition.isFinal()) {
+ finalLibraryFields.add(definition);
+ } else {
+ assert false : "Field `" + field.toSourceString() + "` is not final";
+ }
+ }
+ });
+ }
+ }
+
+ /** Returns true if it is safe to assume that the given library field is final. */
+ public boolean isFinalLibraryField(DexEncodedField field) {
+ return finalLibraryFields.contains(field);
+ }
+
/**
* Returns true if {@param type} is a library type that is modeled in the compiler.
*
@@ -77,9 +106,7 @@
@Override
public void optimize(
- IRCode code,
- OptimizationFeedback feedback,
- MethodProcessor methodProcessor) {
+ IRCode code, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
InstructionListIterator instructionIterator = code.instructionListIterator();
while (instructionIterator.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index f76aeae..c78a123 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -48,6 +48,7 @@
affectedValues.addAll(outValue.affectedValues());
outValue.replaceUsers(inValue);
}
+ // TODO(b/152853271): Debugging information is lost here (DebugLocalWrite may be required).
instructionIterator.removeOrReplaceByDebugLocalRead();
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index 09c8f4e..0713076 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -39,7 +39,7 @@
InvokeMethod invoke,
DexEncodedMethod singleTarget,
Set<Value> affectedValues) {
- if (singleTarget.method == dexItemFactory.stringMethods.equals) {
+ if (singleTarget.method == dexItemFactory.stringMembers.equals) {
optimizeEquals(code, instructionIterator, invoke);
}
}
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 4a1924b..1d679ecc 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
@@ -13,6 +13,7 @@
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.DexType;
@@ -73,6 +74,7 @@
private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
private final Set<DexEncodedMethod> methodsToBeStaticized = Sets.newIdentityHashSet();
private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
+ private final Map<DexMethod, CandidateInfo> singletonGetters = new IdentityHashMap<>();
private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
StaticizingProcessor(
@@ -102,7 +104,7 @@
.add(collectOptimizationInfo(feedback)));
// Enqueue instance methods to be staticized (only remove references to 'this'). Intentionally
- // not collection optimization info for these methods, since they will be reprocessed again
+ // not collecting optimization info for these methods, since they will be reprocessed again
// below once staticized.
enqueueMethodsWithCodeOptimizations(
methodsToBeStaticized, optimizations -> optimizations.add(this::removeReferencesToThis));
@@ -211,19 +213,36 @@
for (DexEncodedMethod method : info.referencedFrom) {
IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
assert code != null;
- List<StaticGet> singletonFieldReads =
+ List<Instruction> singletonUsers =
Streams.stream(code.instructionIterator())
- .filter(Instruction::isStaticGet)
- .map(Instruction::asStaticGet)
- .filter(get -> get.getField() == info.singletonField.field)
+ .filter(
+ instruction -> {
+ if (instruction.isStaticGet()
+ && instruction.asStaticGet().getField() == info.singletonField.field) {
+ return true;
+ }
+ DexEncodedMethod getter = info.getter.get();
+ return getter != null
+ && instruction.isInvokeStatic()
+ && instruction.asInvokeStatic().getInvokedMethod() == getter.method;
+ })
.collect(Collectors.toList());
boolean fixableFieldReadsPerUsage = true;
- for (StaticGet read : singletonFieldReads) {
- Value dest = read.dest();
+ for (Instruction user : singletonUsers) {
+ if (user.outValue() == null) {
+ continue;
+ }
+ Value dest = user.outValue();
visited.clear();
trivialPhis.clear();
- boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfSameFieldRead(
- visited, dest.uniquePhiUsers(), read.getField(), trivialPhis);
+ assert user.isInvokeStatic() || user.isStaticGet();
+ DexMember member =
+ user.isStaticGet()
+ ? user.asStaticGet().getField()
+ : user.asInvokeStatic().getInvokedMethod();
+ boolean onlyHasTrivialPhis =
+ testAndCollectPhisComposedOfSameMember(
+ visited, dest.uniquePhiUsers(), member, trivialPhis);
if (dest.hasPhiUsers() && !onlyHasTrivialPhis) {
fixableFieldReadsPerUsage = false;
break;
@@ -263,6 +282,10 @@
}
}
singletonFields.put(candidate.singletonField.field, candidate);
+ DexEncodedMethod getter = candidate.getter.get();
+ if (getter != null) {
+ singletonGetters.put(getter.method, candidate);
+ }
referencingExtraMethods.addAll(candidate.referencedFrom);
}
@@ -380,28 +403,38 @@
}
private void rewriteReferences(IRCode code, MethodProcessor methodProcessor) {
- // Process all singleton field reads and rewrite their users.
- List<StaticGet> singletonFieldReads =
+ // Fetch all instructions that reference singletons to avoid concurrent modifications to the
+ // instruction list that can arise from doing it directly in the iterator.
+ List<Instruction> singletonUsers =
Streams.stream(code.instructionIterator())
- .filter(Instruction::isStaticGet)
- .map(Instruction::asStaticGet)
- .filter(get -> singletonFields.containsKey(get.getField()))
+ .filter(
+ instruction ->
+ (instruction.isStaticGet()
+ && singletonFields.containsKey(
+ instruction.asFieldInstruction().getField()))
+ || (instruction.isInvokeStatic()
+ && singletonGetters.containsKey(
+ instruction.asInvokeStatic().getInvokedMethod())))
.collect(Collectors.toList());
-
- singletonFieldReads.forEach(
- read -> {
- DexField field = read.getField();
- CandidateInfo candidateInfo = singletonFields.get(field);
- assert candidateInfo != null;
- Value value = read.dest();
- if (value != null) {
- fixupStaticizedFieldReadUsers(code, value, field);
- }
- if (!candidateInfo.preserveRead.get()) {
- read.removeOrReplaceByDebugLocalRead(code);
- }
- });
-
+ for (Instruction singletonUser : singletonUsers) {
+ CandidateInfo candidateInfo;
+ DexMember member;
+ if (singletonUser.isStaticGet()) {
+ candidateInfo = singletonFields.get(singletonUser.asStaticGet().getField());
+ member = singletonUser.asStaticGet().getField();
+ } else {
+ assert singletonUser.isInvokeStatic();
+ candidateInfo = singletonGetters.get(singletonUser.asInvokeStatic().getInvokedMethod());
+ member = singletonUser.asInvokeStatic().getInvokedMethod();
+ }
+ Value value = singletonUser.outValue();
+ if (value != null) {
+ fixupStaticizedFieldUsers(code, value, member);
+ }
+ if (!candidateInfo.preserveRead.get()) {
+ singletonUser.removeOrReplaceByDebugLocalRead(code);
+ }
+ }
if (!candidateToHostMapping.isEmpty()) {
remapMovedCandidates(code);
}
@@ -468,7 +501,7 @@
// invoke-virtual { s1, ... } mtd1
// goto Exit
// b2:
- // s2 <- static-get singleton
+ // s2 <- invoke-static getter()
// ...
// invoke-virtual { s2, ... } mtd1
// goto Exit
@@ -482,7 +515,7 @@
// ...
// goto Exit
// b2:
- // s2 <- static-get singleton
+ // s2 <- invoke-static getter()
// ...
// goto Exit
// Exit:
@@ -493,8 +526,8 @@
// From staticizer's viewpoint, `sp` is trivial in the sense that it is composed of values that
// refer to the same singleton field. If so, we can safely relax the assertion; remove uses of
// field reads; remove quasi-trivial phis; and then remove original field reads.
- private boolean testAndCollectPhisComposedOfSameFieldRead(
- Set<Phi> visited, Set<Phi> phisToCheck, DexField field, Set<Phi> trivialPhis) {
+ private boolean testAndCollectPhisComposedOfSameMember(
+ Set<Phi> visited, Set<Phi> phisToCheck, DexMember dexMember, Set<Phi> trivialPhis) {
for (Phi phi : phisToCheck) {
if (!visited.add(phi)) {
continue;
@@ -505,16 +538,20 @@
if (v.isPhi()) {
chainedPhis.add(operand.asPhi());
} else {
- if (!v.definition.isStaticGet()) {
+ Instruction definition = v.definition;
+ if (!definition.isStaticGet() && !definition.isInvokeStatic()) {
return false;
}
- if (v.definition.asStaticGet().getField() != field) {
+ if (definition.isStaticGet() && definition.asStaticGet().getField() != dexMember) {
+ return false;
+ } else if (definition.isInvokeStatic()
+ && definition.asInvokeStatic().getInvokedMethod() != dexMember) {
return false;
}
}
}
if (!chainedPhis.isEmpty()) {
- if (!testAndCollectPhisComposedOfSameFieldRead(visited, chainedPhis, field, trivialPhis)) {
+ if (!testAndCollectPhisComposedOfSameMember(visited, chainedPhis, dexMember, trivialPhis)) {
return false;
}
}
@@ -525,13 +562,14 @@
// Fixup field read usages. Same as {@link #fixupStaticizedThisUsers} except this one determines
// quasi-trivial phis, based on the original field.
- private void fixupStaticizedFieldReadUsers(IRCode code, Value dest, DexField field) {
+ private void fixupStaticizedFieldUsers(IRCode code, Value dest, DexMember member) {
assert dest != null;
// During the examine phase, field reads with any phi users have been invalidated, hence zero.
// However, it may be not true if re-processing introduces phis after optimizing common suffix.
Set<Phi> trivialPhis = Sets.newIdentityHashSet();
- boolean onlyHasTrivialPhis = testAndCollectPhisComposedOfSameFieldRead(
- Sets.newIdentityHashSet(), dest.uniquePhiUsers(), field, trivialPhis);
+ boolean onlyHasTrivialPhis =
+ testAndCollectPhisComposedOfSameMember(
+ Sets.newIdentityHashSet(), dest.uniquePhiUsers(), member, trivialPhis);
assert !dest.hasPhiUsers() || onlyHasTrivialPhis;
assert trivialPhis.isEmpty() || onlyHasTrivialPhis;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 140c844..07d330b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -852,7 +852,7 @@
public boolean isToStringMethod(DexMethod method) {
return method == factory.stringBuilderMethods.toString
|| method == factory.stringBufferMethods.toString
- || method == factory.stringMethods.valueOf;
+ || method == factory.stringMembers.valueOf;
}
private boolean canHandleArgumentType(DexType argType) {
@@ -910,7 +910,7 @@
for (Instruction outUser : out.uniqueUsers()) {
if (outUser.isInvokeMethodWithReceiver()
&& outUser.asInvokeMethodWithReceiver().getInvokedMethod()
- == factory.stringMethods.intern) {
+ == factory.stringMembers.intern) {
numberOfBuildersWhoseResultIsInterned++;
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index a2b8eb5..cc8a24f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -191,7 +191,7 @@
continue;
}
- if (invokedMethod == factory.stringMethods.trim) {
+ if (invokedMethod == factory.stringMembers.trim) {
Value receiver = invoke.getReceiver().getAliasedValue();
if (receiver.hasLocalInfo() || receiver.isPhi() || !receiver.definition.isConstString()) {
continue;
@@ -210,7 +210,7 @@
Function<DexString, Integer> operatorWithNoArg = null;
BiFunction<DexString, DexString, Integer> operatorWithString = null;
BiFunction<DexString, Integer, Integer> operatorWithInt = null;
- if (invokedMethod == factory.stringMethods.hashCode) {
+ if (invokedMethod == factory.stringMembers.hashCode) {
operatorWithNoArg = rcv -> {
try {
return rcv.decodedHashCode();
@@ -219,33 +219,33 @@
throw new Unreachable();
}
};
- } else if (invokedMethod == factory.stringMethods.length) {
+ } else if (invokedMethod == factory.stringMembers.length) {
operatorWithNoArg = rcv -> rcv.size;
- } else if (invokedMethod == factory.stringMethods.isEmpty) {
+ } else if (invokedMethod == factory.stringMembers.isEmpty) {
operatorWithNoArg = rcv -> rcv.size == 0 ? 1 : 0;
- } else if (invokedMethod == factory.stringMethods.contains) {
+ } else if (invokedMethod == factory.stringMembers.contains) {
operatorWithString = (rcv, arg) -> rcv.toString().contains(arg.toString()) ? 1 : 0;
- } else if (invokedMethod == factory.stringMethods.startsWith) {
+ } else if (invokedMethod == factory.stringMembers.startsWith) {
operatorWithString = (rcv, arg) -> rcv.startsWith(arg) ? 1 : 0;
- } else if (invokedMethod == factory.stringMethods.endsWith) {
+ } else if (invokedMethod == factory.stringMembers.endsWith) {
operatorWithString = (rcv, arg) -> rcv.endsWith(arg) ? 1 : 0;
- } else if (invokedMethod == factory.stringMethods.equals) {
+ } else if (invokedMethod == factory.stringMembers.equals) {
operatorWithString = (rcv, arg) -> rcv.equals(arg) ? 1 : 0;
- } else if (invokedMethod == factory.stringMethods.equalsIgnoreCase) {
+ } else if (invokedMethod == factory.stringMembers.equalsIgnoreCase) {
operatorWithString = (rcv, arg) -> rcv.toString().equalsIgnoreCase(arg.toString()) ? 1 : 0;
- } else if (invokedMethod == factory.stringMethods.contentEqualsCharSequence) {
+ } else if (invokedMethod == factory.stringMembers.contentEqualsCharSequence) {
operatorWithString = (rcv, arg) -> rcv.toString().contentEquals(arg.toString()) ? 1 : 0;
- } else if (invokedMethod == factory.stringMethods.indexOfInt) {
+ } else if (invokedMethod == factory.stringMembers.indexOfInt) {
operatorWithInt = (rcv, idx) -> rcv.toString().indexOf(idx);
- } else if (invokedMethod == factory.stringMethods.indexOfString) {
+ } else if (invokedMethod == factory.stringMembers.indexOfString) {
operatorWithString = (rcv, arg) -> rcv.toString().indexOf(arg.toString());
- } else if (invokedMethod == factory.stringMethods.lastIndexOfInt) {
+ } else if (invokedMethod == factory.stringMembers.lastIndexOfInt) {
operatorWithInt = (rcv, idx) -> rcv.toString().lastIndexOf(idx);
- } else if (invokedMethod == factory.stringMethods.lastIndexOfString) {
+ } else if (invokedMethod == factory.stringMembers.lastIndexOfString) {
operatorWithString = (rcv, arg) -> rcv.toString().lastIndexOf(arg.toString());
- } else if (invokedMethod == factory.stringMethods.compareTo) {
+ } else if (invokedMethod == factory.stringMembers.compareTo) {
operatorWithString = (rcv, arg) -> rcv.toString().compareTo(arg.toString());
- } else if (invokedMethod == factory.stringMethods.compareToIgnoreCase) {
+ } else if (invokedMethod == factory.stringMembers.compareToIgnoreCase) {
operatorWithString = (rcv, arg) -> rcv.toString().compareToIgnoreCase(arg.toString());
} else {
continue;
@@ -513,7 +513,7 @@
if (instr.isInvokeStatic()) {
InvokeStatic invoke = instr.asInvokeStatic();
DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod != factory.stringMethods.valueOf) {
+ if (invokedMethod != factory.stringMembers.valueOf) {
continue;
}
assert invoke.inValues().size() == 1;
@@ -546,7 +546,7 @@
} else if (instr.isInvokeVirtual()) {
InvokeVirtual invoke = instr.asInvokeVirtual();
DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod != factory.stringMethods.toString) {
+ if (invokedMethod != factory.stringMembers.toString) {
continue;
}
assert invoke.inValues().size() == 1;
@@ -596,9 +596,9 @@
if (escapeRoute.isInvokeMethod()) {
DexMethod invokedMethod = escapeRoute.asInvokeMethod().getInvokedMethod();
// b/120138731: Only allow known simple operations on const-string
- if (invokedMethod == appView.dexItemFactory().stringMethods.hashCode
- || invokedMethod == appView.dexItemFactory().stringMethods.isEmpty
- || invokedMethod == appView.dexItemFactory().stringMethods.length) {
+ if (invokedMethod == appView.dexItemFactory().stringMembers.hashCode
+ || invokedMethod == appView.dexItemFactory().stringMembers.isEmpty
+ || invokedMethod == appView.dexItemFactory().stringMembers.length) {
return true;
}
// Add more cases to filter out, if any.
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 fbfacb8..b30d567 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
@@ -77,7 +77,7 @@
instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
instructions.add(new CfConstString(field.name));
instructions.add(
- new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMethods.equals, false));
+ new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMembers.equals, false));
instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest));
instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
instructions.add(new CfReturn(ValueType.INT));
@@ -94,7 +94,7 @@
.createString(
"No enum constant " + enumType.toSourceString().replace('$', '.') + ".")));
instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
- instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMethods.concat, false));
+ instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMembers.concat, false));
instructions.add(
new CfInvoke(
Opcodes.INVOKESPECIAL,
diff --git a/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java b/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
deleted file mode 100644
index ea1ddc3..0000000
--- a/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin;
-
-import static kotlinx.metadata.FlagsKt.flagsOf;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.List;
-import java.util.function.Function;
-import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeParameter;
-import kotlinx.metadata.KmTypeProjection;
-import kotlinx.metadata.KmVariance;
-
-class ClassTypeSignatureToRenamedKmTypeConverter implements ClassTypeSignature.Converter<KmType> {
-
- private final AppView<AppInfoWithLiveness> appView;
- private final List<KmTypeParameter> typeParameters;
- private final Function<DexType, String> toRenamedClassClassifier;
-
- ClassTypeSignatureToRenamedKmTypeConverter(
- AppView<AppInfoWithLiveness> appView,
- List<KmTypeParameter> typeParameters,
- Function<DexType, String> toRenamedClassClassifier) {
- this.appView = appView;
- this.typeParameters = typeParameters;
- this.toRenamedClassClassifier = toRenamedClassClassifier;
- }
-
- @Override
- public KmType init() {
- return new KmType(flagsOf());
- }
-
- @Override
- public KmType visitType(DexType type, KmType result) {
- String classifier = toRenamedClassClassifier.apply(type);
- if (classifier == null) {
- return null;
- }
- result.visitClass(classifier);
- return result;
- }
-
- @Override
- public KmType visitTypeArgument(FieldTypeSignature typeArgument, KmType result) {
- if (result == null) {
- return null;
- }
- List<KmTypeProjection> arguments = result.getArguments();
- KotlinMetadataSynthesizerUtils.populateKmTypeFromSignature(
- typeArgument,
- () -> {
- KmType kmTypeArgument = new KmType(flagsOf());
- arguments.add(new KmTypeProjection(KmVariance.INVARIANT, kmTypeArgument));
- return kmTypeArgument;
- },
- typeParameters,
- appView.dexItemFactory());
- return result;
- }
-
- @Override
- public KmType visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, KmType result) {
- // Do nothing
- return result;
- }
-
- public List<KmTypeParameter> getTypeParameters() {
- return typeParameters;
- }
-}
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 a1cdd66..b4308b9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -40,6 +40,11 @@
public final Intrinsics intrinsics;
public final Metadata metadata;
+ public static final class ClassClassifiers {
+
+ public static final String arrayBinaryName = NAME + "/Array";
+ }
+
// Mappings from JVM types to Kotlin types (of type DexType)
final Map<DexType, DexType> knownTypeConversion;
@@ -57,6 +62,7 @@
// https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/index.html
// Boxed primitives and arrays
.put(factory.booleanType, factory.createType(addKotlinPrefix("Boolean;")))
+ .put(factory.boxedBooleanType, factory.createType(addKotlinPrefix("Boolean;")))
.put(factory.booleanArrayType, factory.createType(addKotlinPrefix("BooleanArray;")))
.put(factory.byteType, factory.createType(addKotlinPrefix("Byte;")))
.put(factory.byteArrayType, factory.createType(addKotlinPrefix("ByteArray;")))
@@ -65,6 +71,7 @@
.put(factory.shortType, factory.createType(addKotlinPrefix("Short;")))
.put(factory.shortArrayType, factory.createType(addKotlinPrefix("ShortArray;")))
.put(factory.intType, factory.createType(addKotlinPrefix("Int;")))
+ .put(factory.boxedIntType, factory.createType(addKotlinPrefix("Int;")))
.put(factory.intArrayType, factory.createType(addKotlinPrefix("IntArray;")))
.put(factory.longType, factory.createType(addKotlinPrefix("Long;")))
.put(factory.longArrayType, factory.createType(addKotlinPrefix("LongArray;")))
@@ -96,13 +103,17 @@
factory.createType(addKotlinPrefix("collections/ListIterator;")))
.put(factory.iterableType, factory.createType(addKotlinPrefix("collections/Iterable;")))
.put(
- factory.mapEntryType,
- factory.createType(addKotlinPrefix("collections/Map$Entry;")))
+ factory.mapEntryType, factory.createType(addKotlinPrefix("collections/Map$Entry;")))
// .../jvm/functions/FunctionN -> .../FunctionN
.putAll(
- IntStream.rangeClosed(0, 22).boxed().collect(Collectors.toMap(
- i -> factory.createType(addKotlinPrefix("jvm/functions/Function" + i + ";")),
- i -> factory.createType(addKotlinPrefix("Function" + i + ";")))))
+ IntStream.rangeClosed(0, 22)
+ .boxed()
+ .collect(
+ Collectors.toMap(
+ i ->
+ factory.createType(
+ addKotlinPrefix("jvm/functions/Function" + i + ";")),
+ i -> factory.createType(addKotlinPrefix("Function" + i + ";")))))
.build();
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index c891897..db37d8c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -83,16 +83,12 @@
}
}
- ClassTypeSignatureToRenamedKmTypeConverter converter =
- new ClassTypeSignatureToRenamedKmTypeConverter(
- appView, getTypeParameters(), synthesizer::toRenamedClassifier);
-
// Rewriting upward hierarchy.
List<KmType> superTypes = kmClass.getSupertypes();
superTypes.clear();
for (DexType itfType : clazz.interfaces.values) {
// TODO(b/129925954): Use GenericSignature.ClassSignature#superInterfaceSignatures
- KmType kmType = synthesizer.toRenamedKmType(itfType, null, null, converter);
+ KmType kmType = synthesizer.toRenamedKmType(itfType, null, null, getTypeParameters());
if (kmType != null) {
superTypes.add(kmType);
}
@@ -101,7 +97,7 @@
if (clazz.superType != appView.dexItemFactory().objectType) {
// TODO(b/129925954): Use GenericSignature.ClassSignature#superClassSignature
KmType kmTypeForSupertype =
- synthesizer.toRenamedKmType(clazz.superType, null, null, converter);
+ synthesizer.toRenamedKmType(clazz.superType, null, null, getTypeParameters());
if (kmTypeForSupertype != null) {
superTypes.add(kmTypeForSupertype);
}
@@ -151,7 +147,7 @@
if (!method.isInstanceInitializer()) {
continue;
}
- KmConstructor constructor = synthesizer.toRenamedKmConstructor(method);
+ KmConstructor constructor = synthesizer.toRenamedKmConstructor(clazz, method);
if (constructor != null) {
constructors.add(constructor);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
new file mode 100644
index 0000000..c7c3701
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassifierInfo.java
@@ -0,0 +1,49 @@
+// 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 kotlinx.metadata.KmClassifier;
+
+// Provides access to information about a kotlin classifier
+public class KotlinClassifierInfo {
+
+ private static boolean isClass(KmClassifier classifier) {
+ return classifier instanceof KmClassifier.Class;
+ }
+
+ private static KmClassifier.Class getClassClassifier(KmClassifier classifier) {
+ return (KmClassifier.Class) classifier;
+ }
+
+ private static boolean isTypeAlias(KmClassifier classifier) {
+ return classifier instanceof KmClassifier.TypeAlias;
+ }
+
+ private static KmClassifier.TypeAlias getTypeAlias(KmClassifier classifier) {
+ return (KmClassifier.TypeAlias) classifier;
+ }
+
+ private static boolean isTypeParameter(KmClassifier classifier) {
+ return classifier instanceof KmClassifier.TypeParameter;
+ }
+
+ private static KmClassifier.TypeParameter getTypeParameter(KmClassifier classifier) {
+ return (KmClassifier.TypeParameter) classifier;
+ }
+
+ public static boolean equals(KmClassifier one, KmClassifier other) {
+ if (isClass(one)) {
+ return isClass(other)
+ && getClassClassifier(one).getName().equals(getClassClassifier(other).getName());
+ }
+ if (isTypeAlias(one)) {
+ return isTypeAlias(other)
+ && getTypeAlias(one).getName().equals(getTypeAlias(other).getName());
+ }
+ assert isTypeParameter(one);
+ return isTypeParameter(other)
+ && getTypeParameter(one).getId() == getTypeParameter(other).getId();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 64ed559..e516ff9 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -18,10 +18,12 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
@@ -146,6 +148,7 @@
KmDeclarationContainer kmDeclarationContainer = getDeclarations();
rewriteFunctions(synthesizer, kmDeclarationContainer.getFunctions());
rewriteProperties(synthesizer, kmDeclarationContainer.getProperties());
+ rewriteTypeAliases(synthesizer, kmDeclarationContainer.getTypeAliases());
}
private void rewriteFunctions(KotlinMetadataSynthesizer synthesizer, List<KmFunction> functions) {
@@ -164,6 +167,37 @@
}
}
+ private void rewriteTypeAliases(
+ KotlinMetadataSynthesizer synthesizer, List<KmTypeAlias> typeAliases) {
+ Iterator<KmTypeAlias> iterator = typeAliases.iterator();
+ while (iterator.hasNext()) {
+ KmTypeAlias typeAlias = iterator.next();
+ KotlinTypeInfo expandedRenamed =
+ KotlinTypeInfo.create(typeAlias.expandedType).toRenamed(synthesizer);
+ if (expandedRenamed == null) {
+ // If the expanded type is pruned, the type-alias is also removed. Type-aliases can refer to
+ // other type-aliases in the underlying type, however, we only remove a type-alias when the
+ // expanded type is removed making it impossible to construct any type that references the
+ // type-alias anyway.
+ // TODO(b/151719926): Add a test for the above.
+ iterator.remove();
+ continue;
+ }
+ typeAlias.setExpandedType(expandedRenamed.asKmType());
+ // Modify the underlying type (right-hand side) of the type-alias.
+ KotlinTypeInfo underlyingRenamed =
+ KotlinTypeInfo.create(typeAlias.underlyingType).toRenamed(synthesizer);
+ if (underlyingRenamed == null) {
+ Reporter reporter = synthesizer.appView.options().reporter;
+ reporter.warning(
+ KotlinMetadataDiagnostic.messageInvalidUnderlyingType(clazz, typeAlias.getName()));
+ iterator.remove();
+ continue;
+ }
+ typeAlias.setUnderlyingType(underlyingRenamed.asKmType());
+ }
+ }
+
private void rewriteProperties(
KotlinMetadataSynthesizer synthesizer, List<KmProperty> properties) {
Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
@@ -599,9 +633,10 @@
"outerType",
sb,
nextIndent -> appendKmType(newIndent, sb, kmType.getOuterType()));
+ appendKeyValue(newIndent, "raw", sb, JvmExtensionsKt.isRaw(kmType) + "");
appendKeyValue(
newIndent,
- "extensions",
+ "annotations",
sb,
nextIndent -> {
appendKmList(
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
index ed7b49f..142aef3 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -21,7 +21,6 @@
import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -29,14 +28,15 @@
import kotlinx.metadata.KmDeclarationContainer;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmProperty;
-import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
import kotlinx.metadata.KmValueParameter;
import kotlinx.metadata.jvm.JvmMethodSignature;
// Provides access to field/method-level Kotlin information.
public abstract class KotlinMemberInfo {
- private static final List<KotlinValueParameterInfo> EMPTY_PARAM_INFO = ImmutableList.of();
+ private static final List<KotlinValueParameterInfo> EMPTY_VALUE_PARAM_INFO = ImmutableList.of();
+ static final List<KotlinTypeParameterInfo> EMPTY_TYPE_PARAM_INFO = ImmutableList.of();
private static final KotlinMemberInfo NO_KOTLIN_MEMBER_INFO = new NoKotlinMemberInfo();
@@ -90,16 +90,24 @@
final List<KotlinValueParameterInfo> valueParameterInfos;
// Information from original KmFunction.returnType. Null if this is from a KmConstructor.
public final KotlinTypeInfo returnType;
+ // Information from original KmFunction.receiverType. Null if this is from a KmConstructor.
+ final KotlinTypeInfo receiverParameterType;
+ // Information about original type parameters. Null if this is from a KmConstructor.
+ final List<KotlinTypeParameterInfo> kotlinTypeParameterInfo;
private KotlinFunctionInfo(
MemberKind memberKind,
int flags,
KotlinTypeInfo returnType,
- List<KotlinValueParameterInfo> valueParameterInfos) {
+ KotlinTypeInfo receiverParameterType,
+ List<KotlinValueParameterInfo> valueParameterInfos,
+ List<KotlinTypeParameterInfo> kotlinTypeParameterInfo) {
super(memberKind, flags);
assert memberKind.isFunction() || memberKind.isConstructor();
this.returnType = returnType;
+ this.receiverParameterType = receiverParameterType;
this.valueParameterInfos = valueParameterInfos;
+ this.kotlinTypeParameterInfo = kotlinTypeParameterInfo;
}
KotlinValueParameterInfo getValueParameterInfo(int i) {
@@ -189,32 +197,51 @@
}
private static KotlinFunctionInfo createFunctionInfoFromConstructor(KmConstructor kmConstructor) {
- return createFunctionInfo(
- CONSTRUCTOR, kmConstructor.getFlags(), null, kmConstructor.getValueParameters());
+ return new KotlinFunctionInfo(
+ CONSTRUCTOR,
+ kmConstructor.getFlags(),
+ null,
+ null,
+ getValueParameters(kmConstructor.getValueParameters()),
+ EMPTY_TYPE_PARAM_INFO);
}
private static KotlinFunctionInfo createFunctionInfo(
MemberKind memberKind, KmFunction kmFunction) {
- return createFunctionInfo(
+ assert memberKind.isFunction();
+ KotlinTypeInfo returnTypeInfo = KotlinTypeInfo.create(kmFunction.getReturnType());
+ KotlinTypeInfo receiverParameterTypeInfo =
+ KotlinTypeInfo.create(kmFunction.getReceiverParameterType());
+ return new KotlinFunctionInfo(
memberKind,
kmFunction.getFlags(),
- kmFunction.getReturnType(),
- kmFunction.getValueParameters());
+ returnTypeInfo,
+ receiverParameterTypeInfo,
+ getValueParameters(kmFunction.getValueParameters()),
+ getTypeParameters(kmFunction.getTypeParameters()));
}
- private static KotlinFunctionInfo createFunctionInfo(
- MemberKind memberKind, int flags, KmType returnType, List<KmValueParameter> valueParameters) {
- assert memberKind.isFunction() || memberKind.isConstructor();
- KotlinTypeInfo returnTypeInfo = KotlinTypeInfo.create(returnType);
- assert memberKind.isFunction() || memberKind.isConstructor();
- if (valueParameters.isEmpty()) {
- return new KotlinFunctionInfo(memberKind, flags, returnTypeInfo, EMPTY_PARAM_INFO);
+ private static List<KotlinValueParameterInfo> getValueParameters(
+ List<KmValueParameter> parameters) {
+ if (parameters.isEmpty()) {
+ return EMPTY_VALUE_PARAM_INFO;
}
- List<KotlinValueParameterInfo> valueParameterInfos = new ArrayList<>(valueParameters.size());
- for (KmValueParameter kmValueParameter : valueParameters) {
- valueParameterInfos.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
+ ImmutableList.Builder<KotlinValueParameterInfo> builder = ImmutableList.builder();
+ for (KmValueParameter kmValueParameter : parameters) {
+ builder.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
}
- return new KotlinFunctionInfo(memberKind, flags, returnTypeInfo, valueParameterInfos);
+ return builder.build();
+ }
+
+ private static List<KotlinTypeParameterInfo> getTypeParameters(List<KmTypeParameter> parameters) {
+ if (parameters.isEmpty()) {
+ return EMPTY_TYPE_PARAM_INFO;
+ }
+ ImmutableList.Builder<KotlinTypeParameterInfo> builder = ImmutableList.builder();
+ for (KmTypeParameter kmTypeParameter : parameters) {
+ builder.add(KotlinTypeParameterInfo.fromKmTypeParameter(kmTypeParameter));
+ }
+ return builder.build();
}
private static KotlinFieldInfo createFieldInfo(MemberKind memberKind, KmProperty kmProperty) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
new file mode 100644
index 0000000..da33940
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataDiagnostic.java
@@ -0,0 +1,49 @@
+// 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.Diagnostic;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+public class KotlinMetadataDiagnostic implements Diagnostic {
+
+ private final Origin origin;
+ private final Position position;
+ private final String message;
+
+ public KotlinMetadataDiagnostic(Origin origin, Position position, String message) {
+ this.origin = origin;
+ this.position = position;
+ this.message = message;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ @Override
+ public Position getPosition() {
+ return position;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return message;
+ }
+
+ static KotlinMetadataDiagnostic messageInvalidUnderlyingType(DexClass clazz, String typeAlias) {
+ return new KotlinMetadataDiagnostic(
+ clazz.getOrigin(),
+ Position.UNKNOWN,
+ "The type alias "
+ + typeAlias
+ + " in class "
+ + clazz.type.getName()
+ + " has an invalid underlying type. The type-alias is removed from the output.");
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 2e90094..bed05c0 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,13 +3,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.kotlin;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.EMPTY_TYPE_PARAM_INFO;
import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.populateKmTypeFromSignature;
import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.toClassifier;
import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
-import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
import static kotlinx.metadata.Flag.Property.IS_VAR;
import static kotlinx.metadata.FlagsKt.flagsOf;
@@ -23,18 +23,22 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GenericSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinFunctionInfo;
import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
+import com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.AddKotlinAnyType;
+import com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.KmVisitorOption;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.Box;
+import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.function.Consumer;
+import kotlinx.metadata.KmClassifier;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmProperty;
@@ -47,8 +51,8 @@
class KotlinMetadataSynthesizer {
- private final AppView<AppInfoWithLiveness> appView;
- private final NamingLens lens;
+ final AppView<AppInfoWithLiveness> appView;
+ final NamingLens lens;
private final List<KmTypeParameter> classTypeParameters;
public KotlinMetadataSynthesizer(
@@ -107,49 +111,45 @@
DexType type,
TypeSignature typeSignature,
KotlinTypeInfo originalKotlinTypeInfo,
- ClassTypeSignatureToRenamedKmTypeConverter converter) {
+ List<KmTypeParameter> typeParameters) {
if (originalKotlinTypeInfo != null && originalKotlinTypeInfo.isTypeAlias()) {
KmType kmType = new KmType(flagsOf());
kmType.visitTypeAlias(originalKotlinTypeInfo.asTypeAlias().getName());
return kmType;
}
- return toRenamedKmTypeWithClassifier(type, typeSignature, converter);
+ return toRenamedKmTypeWithClassifier(
+ type, originalKotlinTypeInfo, typeSignature, typeParameters);
}
private KmType toRenamedKmTypeWithClassifierForFieldSignature(
- DexType type,
+ KotlinTypeInfo originalTypeInfo,
FieldTypeSignature fieldSignature,
- ClassTypeSignatureToRenamedKmTypeConverter converter) {
- if (fieldSignature.isClassTypeSignature()) {
- ClassTypeSignature classTypeSignature = fieldSignature.asClassTypeSignature();
- if (classTypeSignature.type() != type) {
- return null;
- }
- return classTypeSignature.convert(converter);
- }
- // If we fail to set kmType.classifier we will get a UninitializedPropertyAccessException:
- // lateinit property classifier has not been initialized.
+ List<KmTypeParameter> typeParameters) {
Box<KmType> kmTypeBox = new Box<>();
populateKmTypeFromSignature(
fieldSignature,
- () -> {
+ originalTypeInfo,
+ (kmVisitorOption) -> {
+ assert kmVisitorOption == KmVisitorOption.VISIT_NEW;
KmType value = new KmType(flagsOf());
kmTypeBox.set(value);
return value;
},
- converter.getTypeParameters(),
- appView.dexItemFactory());
+ typeParameters,
+ appView.dexItemFactory(),
+ AddKotlinAnyType.ADD);
return kmTypeBox.get();
}
private KmType toRenamedKmTypeWithClassifier(
DexType type,
+ KotlinTypeInfo originalTypeInfo,
TypeSignature typeSignature,
- ClassTypeSignatureToRenamedKmTypeConverter converter) {
+ List<KmTypeParameter> typeParameters) {
if (typeSignature != null && typeSignature.isFieldTypeSignature()) {
KmType renamedKmType =
toRenamedKmTypeWithClassifierForFieldSignature(
- type, typeSignature.asFieldTypeSignature(), converter);
+ originalTypeInfo, typeSignature.asFieldTypeSignature(), typeParameters);
if (renamedKmType != null) {
return renamedKmType;
}
@@ -165,34 +165,93 @@
// E.g., java.lang.String[] -> KmType(kotlin/Array, KmTypeProjection(OUT, kotlin/String))
if (type.isArrayType() && !type.isPrimitiveArrayType()) {
DexType elementType = type.toArrayElementType(appView.dexItemFactory());
- KmType argumentType = toRenamedKmTypeWithClassifier(elementType, null, converter);
- renamedKmType.getArguments().add(new KmTypeProjection(KmVariance.OUT, argumentType));
+ KmType argumentType = toRenamedKmTypeWithClassifier(elementType, null, null, typeParameters);
+ KmVariance variance =
+ originalTypeInfo != null && originalTypeInfo.isObjectArray()
+ ? originalTypeInfo.getArguments().get(0).variance
+ : KmVariance.INVARIANT;
+ renamedKmType.getArguments().add(new KmTypeProjection(variance, argumentType));
}
return renamedKmType;
}
- KmConstructor toRenamedKmConstructor(DexEncodedMethod method) {
+ KmConstructor toRenamedKmConstructor(DexClass clazz, DexEncodedMethod method) {
// Make sure it is an instance initializer and live.
if (!method.isInstanceInitializer()
|| !appView.appInfo().liveMethods.contains(method.method)) {
return null;
}
- KmConstructor kmConstructor = new KmConstructor(method.accessFlags.getAsKotlinFlags());
+ // Take access flags from metadata.
+ KotlinFunctionInfo kotlinFunctionInfo = method.getKotlinMemberInfo().asFunctionInfo();
+ int flags;
+ List<KotlinTypeParameterInfo> originalTypeParameterInfo;
+ if (kotlinFunctionInfo != null) {
+ flags = kotlinFunctionInfo.flags;
+ originalTypeParameterInfo = kotlinFunctionInfo.kotlinTypeParameterInfo;
+ } else {
+ flags = method.accessFlags.getAsKotlinFlags();
+ originalTypeParameterInfo = EMPTY_TYPE_PARAM_INFO;
+ }
+ KmConstructor kmConstructor = new KmConstructor(flags);
JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method));
MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
List<KmTypeParameter> typeParameters =
convertFormalTypeParameters(
+ originalTypeParameterInfo,
signature.getFormalTypeParameters(),
kmTypeParameter -> {
assert false : "KmConstructor cannot have additional type parameters";
});
- ClassTypeSignatureToRenamedKmTypeConverter converter =
- new ClassTypeSignatureToRenamedKmTypeConverter(
- appView, typeParameters, this::toRenamedClassifier);
List<KmValueParameter> parameters = kmConstructor.getValueParameters();
- if (!populateKmValueParameters(method, signature, parameters, converter)) {
+ if (!populateKmValueParameters(method, signature, parameters, typeParameters)) {
return null;
}
+ // For inner, non-static classes, the type-parameter for the receiver should not have a
+ // value-parameter:
+ // val myInner : OuterNestedInner = nested.Inner(1)
+ // Will have value-parameters for the constructor:
+ // # constructors: KmConstructor[
+ // # KmConstructor{
+ // # flags: 6,
+ // # valueParameters: KmValueParameter[
+ // # KmValueParameter{
+ // # flags: 0,
+ // # name: x,
+ // # type: KmType{
+ // # flags: 0,
+ // # classifier: Class(name=kotlin/Int),
+ // # arguments: KmTypeProjection[],
+ // # abbreviatedType: null,
+ // # outerType: null,
+ // # raw: false,
+ // # annotations: KmAnnotion[],
+ // # },
+ // # varargElementType: null,
+ // # }
+ // # ],
+ // # signature: <init>(Lcom/android/tools/r8/kotlin/metadata/typealias_lib/Outer$Nested;I)V,
+ // # }
+ // # ],
+ // A bit weird since the signature obviously have two value-parameters.
+ List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
+ if (!parameters.isEmpty() && !innerClasses.isEmpty()) {
+ DexType immediateOuterType = null;
+ for (InnerClassAttribute innerClass : innerClasses) {
+ if (innerClass.getInner() == clazz.type) {
+ immediateOuterType = innerClass.getOuter();
+ break;
+ }
+ }
+ if (immediateOuterType != null) {
+ String classifier = toRenamedClassifier(immediateOuterType);
+ KmType potentialReceiver = parameters.get(0).getType();
+ if (potentialReceiver != null
+ && potentialReceiver.classifier instanceof KmClassifier.Class
+ && ((KmClassifier.Class) potentialReceiver.classifier).getName().equals(classifier)) {
+ parameters.remove(0);
+ }
+ }
+ }
return kmConstructor;
}
@@ -223,18 +282,18 @@
// on demand? That may require utils to map internal encoding of signature back to
// corresponding backend definitions, like DexAnnotation (DEX) or Signature attribute (CF).
MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
- List<KmTypeParameter> typeParameters = kmFunction.getTypeParameters();
- ClassTypeSignatureToRenamedKmTypeConverter converter =
- new ClassTypeSignatureToRenamedKmTypeConverter(
- appView,
- convertFormalTypeParameters(signature.getFormalTypeParameters(), typeParameters::add),
- this::toRenamedClassifier);
-
+ List<KmTypeParameter> methodTypeParameters = kmFunction.getTypeParameters();
DexProto proto = method.method.proto;
DexType returnType = proto.returnType;
TypeSignature returnSignature = signature.returnType().typeSignature();
+ List<KmTypeParameter> allTypeParameters =
+ convertFormalTypeParameters(
+ kotlinMemberInfo.kotlinTypeParameterInfo,
+ signature.getFormalTypeParameters(),
+ methodTypeParameters::add);
KmType kmReturnType =
- toRenamedKmType(returnType, returnSignature, kotlinMemberInfo.returnType, converter);
+ toRenamedKmType(
+ returnType, returnSignature, kotlinMemberInfo.returnType, allTypeParameters);
if (kmReturnType == null) {
return null;
}
@@ -243,30 +302,42 @@
assert proto.parameters.values.length > 0 : method.method.toSourceString();
DexType receiverType = proto.parameters.values[0];
TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
- KmType kmReceiverType = toRenamedKmType(receiverType, receiverSignature, null, converter);
+ KmType kmReceiverType =
+ toRenamedKmType(
+ receiverType,
+ receiverSignature,
+ kotlinMemberInfo.receiverParameterType,
+ allTypeParameters);
if (kmReceiverType == null) {
return null;
}
kmFunction.setReceiverParameterType(kmReceiverType);
}
- if (!populateKmValueParameters(method, signature, kmFunction.getValueParameters(), converter)) {
+ if (!populateKmValueParameters(
+ method, signature, kmFunction.getValueParameters(), allTypeParameters)) {
return null;
}
return kmFunction;
}
private List<KmTypeParameter> convertFormalTypeParameters(
- List<FormalTypeParameter> parameters, Consumer<KmTypeParameter> addedFromParameters) {
+ List<KotlinTypeParameterInfo> originalTypeParameterInfo,
+ List<FormalTypeParameter> parameters,
+ Consumer<KmTypeParameter> addedFromParameters) {
return KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
- classTypeParameters, parameters, appView.dexItemFactory(), addedFromParameters);
+ classTypeParameters,
+ originalTypeParameterInfo,
+ parameters,
+ appView.dexItemFactory(),
+ addedFromParameters);
}
private boolean populateKmValueParameters(
DexEncodedMethod method,
MethodTypeSignature signature,
List<KmValueParameter> parameters,
- ClassTypeSignatureToRenamedKmTypeConverter converter) {
+ List<KmTypeParameter> typeParameters) {
KotlinFunctionInfo kotlinFunctionInfo = method.getKotlinMemberInfo().asFunctionInfo();
if (kotlinFunctionInfo == null) {
return false;
@@ -281,7 +352,11 @@
TypeSignature parameterTypeSignature = signature.getParameterTypeSignature(i);
KmValueParameter kmValueParameter =
toRewrittenKmValueParameter(
- valueParameterInfo, parameterType, parameterTypeSignature, parameterName, converter);
+ valueParameterInfo,
+ parameterType,
+ parameterTypeSignature,
+ parameterName,
+ typeParameters);
if (kmValueParameter == null) {
return false;
}
@@ -295,13 +370,13 @@
DexType parameterType,
TypeSignature parameterTypeSignature,
String candidateParameterName,
- ClassTypeSignatureToRenamedKmTypeConverter converter) {
+ List<KmTypeParameter> typeParameters) {
int flag = valueParameterInfo != null ? valueParameterInfo.flag : flagsOf();
String name = valueParameterInfo != null ? valueParameterInfo.name : candidateParameterName;
KmValueParameter kmValueParameter = new KmValueParameter(flag, name);
KotlinTypeInfo originalKmTypeInfo = valueParameterInfo != null ? valueParameterInfo.type : null;
KmType kmParamType =
- toRenamedKmType(parameterType, parameterTypeSignature, originalKmTypeInfo, converter);
+ toRenamedKmType(parameterType, parameterTypeSignature, originalKmTypeInfo, typeParameters);
if (kmParamType == null) {
return null;
}
@@ -309,17 +384,17 @@
if (valueParameterInfo != null) {
JvmExtensionsKt.getAnnotations(kmParamType).addAll(valueParameterInfo.annotations);
}
-
if (valueParameterInfo != null && valueParameterInfo.isVararg) {
// TODO(b/152389234): Test for arrays in varargs.
if (!parameterType.isArrayType()) {
return null;
}
+ // vararg x: T gets translated to x: Array<out T>
DexType elementType = parameterType.toArrayElementType(appView.dexItemFactory());
TypeSignature elementSignature =
parameterTypeSignature != null
? parameterTypeSignature.toArrayElementTypeSignature(appView) : null;
- KmType kmElementType = toRenamedKmType(elementType, elementSignature, null, converter);
+ KmType kmElementType = toRenamedKmType(elementType, elementSignature, null, typeParameters);
if (kmElementType == null) {
return null;
}
@@ -488,12 +563,9 @@
NamingLens lens,
KotlinMetadataSynthesizer synthesizer) {
DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
- ClassTypeSignatureToRenamedKmTypeConverter converter =
- new ClassTypeSignatureToRenamedKmTypeConverter(
- appView, this.classTypeParameters, synthesizer::toRenamedClassifier);
if (kmProperty.getReturnType() == defaultPropertyType) {
KmType kmPropertyType =
- synthesizer.toRenamedKmType(field.field.type, null, null, converter);
+ synthesizer.toRenamedKmType(field.field.type, null, null, this.classTypeParameters);
if (kmPropertyType != null) {
kmProperty.setReturnType(kmPropertyType);
}
@@ -512,23 +584,22 @@
}
MethodTypeSignature signature =
GenericSignature.Parser.toMethodTypeSignature(method, appView);
+ // TODO(b/152599446): Update with type parameters for the receiver.
List<KmTypeParameter> typeParameters =
KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
classTypeParameters,
+ KotlinMemberInfo.EMPTY_TYPE_PARAM_INFO,
signature.getFormalTypeParameters(),
appView.dexItemFactory(),
kmTypeParameter -> {});
- ClassTypeSignatureToRenamedKmTypeConverter converter =
- new ClassTypeSignatureToRenamedKmTypeConverter(
- appView, typeParameters, synthesizer::toRenamedClassifier);
DexType receiverType = method.method.proto.parameters.values[0];
TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
KmType kmReceiverType =
- synthesizer.toRenamedKmType(receiverType, receiverSignature, null, converter);
+ synthesizer.toRenamedKmType(receiverType, receiverSignature, null, typeParameters);
if (kmProperty.getReceiverParameterType() != null) {
// If the receiver type for the extension property is set already make sure it's consistent.
- return getDescriptorFromKmType(kmReceiverType)
- .equals(getDescriptorFromKmType(kmProperty.getReceiverParameterType()));
+ return KotlinMetadataSynthesizerUtils.hasEqualClassifier(
+ kmReceiverType, kmProperty.getReceiverParameterType());
}
kmProperty.setReceiverParameterType(kmReceiverType);
return true;
@@ -556,25 +627,24 @@
: "checkGetterCriteria: " + this.toString();
MethodTypeSignature signature =
GenericSignature.Parser.toMethodTypeSignature(getter, appView);
+ // TODO(b/152599446): Update with type parameters for the getter.
List<KmTypeParameter> typeParameters =
KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
this.classTypeParameters,
+ ImmutableList.of(),
signature.getFormalTypeParameters(),
appView.dexItemFactory(),
kmTypeParameter -> {});
- ClassTypeSignatureToRenamedKmTypeConverter converter =
- new ClassTypeSignatureToRenamedKmTypeConverter(
- appView, typeParameters, synthesizer::toRenamedClassifier);
DexType returnType = proto.returnType;
TypeSignature returnSignature = signature.returnType().typeSignature();
KmType kmPropertyType =
synthesizer.toRenamedKmType(
- returnType, returnSignature, getterInfo.returnType, converter);
+ returnType, returnSignature, getterInfo.returnType, typeParameters);
if (kmProperty.getReturnType() == defaultPropertyType) {
// The property type is not set yet.
kmProperty.setReturnType(kmPropertyType);
- } else if (!getDescriptorFromKmType(kmPropertyType)
- .equals(getDescriptorFromKmType(kmProperty.getReturnType()))) {
+ } else if (!KotlinMetadataSynthesizerUtils.hasEqualClassifier(
+ kmPropertyType, kmProperty.getReturnType())) {
// If property type is set already (via backing field), make sure it's consistent.
return null;
}
@@ -610,27 +680,27 @@
: "checkSetterCriteria: " + this.toString();
MethodTypeSignature signature =
GenericSignature.Parser.toMethodTypeSignature(setter, appView);
+ // TODO(b/152599446): Update with type parameters for the setter.
List<KmTypeParameter> typeParameters =
KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
classTypeParameters,
+ ImmutableList.of(),
signature.getFormalTypeParameters(),
appView.dexItemFactory(),
kmTypeParameter -> {});
- ClassTypeSignatureToRenamedKmTypeConverter converter =
- new ClassTypeSignatureToRenamedKmTypeConverter(
- appView, typeParameters, synthesizer::toRenamedClassifier);
int valueIndex = isExtension ? 1 : 0;
DexType valueType = proto.parameters.values[valueIndex];
TypeSignature valueSignature = signature.getParameterTypeSignature(valueIndex);
KmType kmPropertyType =
- synthesizer.toRenamedKmType(valueType, valueSignature, setterInfo.returnType, converter);
+ synthesizer.toRenamedKmType(
+ valueType, valueSignature, setterInfo.returnType, typeParameters);
if (kmProperty.getReturnType() == defaultPropertyType) {
// The property type is not set yet.
kmProperty.setReturnType(kmPropertyType);
} else {
// If property type is set already make sure it's consistent.
- if (!getDescriptorFromKmType(kmPropertyType)
- .equals(getDescriptorFromKmType(kmProperty.getReturnType()))) {
+ if (!KotlinMetadataSynthesizerUtils.hasEqualClassifier(
+ kmPropertyType, kmProperty.getReturnType())) {
return null;
}
}
@@ -639,7 +709,7 @@
setter.getKotlinMemberInfo().asPropertyInfo().valueParameterInfo;
KmValueParameter kmValueParameter =
synthesizer.toRewrittenKmValueParameter(
- valueParameterInfo, valueType, valueSignature, "value", converter);
+ valueParameterInfo, valueType, valueSignature, "value", typeParameters);
if (kmValueParameter != null) {
kmProperty.setSetterParameter(kmValueParameter);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
index 9382cd9..e773cef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
@@ -15,43 +15,74 @@
import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
import com.android.tools.r8.graph.GenericSignature.TypeVariableSignature;
+import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.function.Consumer;
-import java.util.function.Supplier;
+import java.util.function.Function;
+import kotlinx.metadata.KmType;
import kotlinx.metadata.KmTypeParameter;
import kotlinx.metadata.KmTypeVisitor;
import kotlinx.metadata.KmVariance;
class KotlinMetadataSynthesizerUtils {
+ // The KmVisitorOption is used for star projections, otherwise we will have late-init failures
+ // due to visitStarProjection adding an argument.
+ enum KmVisitorOption {
+ VISIT_NEW,
+ VISIT_PARENT
+ }
+
+ // The AddKotlinAnyType is carrying around information regarding the need for adding the trivial
+ // type Kotlin/Any. The information is not consistently added, for example, for upper bounds
+ // the trivial bound is not recorded.
+ public enum AddKotlinAnyType {
+ ADD,
+ DISREGARD
+ }
+
static void populateKmTypeFromSignature(
FieldTypeSignature typeSignature,
- Supplier<KmTypeVisitor> typeVisitor,
+ KotlinTypeInfo originalTypeInfo,
+ Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
List<KmTypeParameter> allTypeParameters,
- DexItemFactory factory) {
+ DexItemFactory factory,
+ AddKotlinAnyType addAny) {
if (typeSignature.isClassTypeSignature()) {
populateKmTypeFromClassTypeSignature(
- typeSignature.asClassTypeSignature(), typeVisitor, allTypeParameters, factory);
+ typeSignature.asClassTypeSignature(),
+ originalTypeInfo,
+ typeVisitor,
+ allTypeParameters,
+ factory,
+ addAny);
} else if (typeSignature.isArrayTypeSignature()) {
populateKmTypeFromArrayTypeSignature(
- typeSignature.asArrayTypeSignature(), typeVisitor, allTypeParameters, factory);
- } else {
- assert typeSignature.isTypeVariableSignature();
+ typeSignature.asArrayTypeSignature(),
+ originalTypeInfo,
+ typeVisitor,
+ allTypeParameters,
+ factory,
+ addAny);
+ } else if (typeSignature.isTypeVariableSignature()) {
populateKmTypeFromTypeVariableSignature(
typeSignature.asTypeVariableSignature(), typeVisitor, allTypeParameters);
+ } else {
+ assert typeSignature.isStar();
+ typeVisitor.apply(KmVisitorOption.VISIT_PARENT).visitStarProjection();
}
}
private static void populateKmTypeFromTypeVariableSignature(
TypeVariableSignature typeSignature,
- Supplier<KmTypeVisitor> visitor,
+ Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
List<KmTypeParameter> allTypeParameters) {
for (KmTypeParameter typeParameter : allTypeParameters) {
if (typeParameter
.getName()
.equals(typeSignature.asTypeVariableSignature().getTypeVariable())) {
- visitor.get().visitTypeParameter(typeParameter.getId());
+ typeVisitor.apply(KmVisitorOption.VISIT_NEW).visitTypeParameter(typeParameter.getId());
return;
}
}
@@ -59,39 +90,79 @@
private static void populateKmTypeFromArrayTypeSignature(
ArrayTypeSignature typeSignature,
- Supplier<KmTypeVisitor> visitor,
+ KotlinTypeInfo originalTypeInfo,
+ Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
List<KmTypeParameter> allTypeParameters,
- DexItemFactory factory) {
+ DexItemFactory factory,
+ AddKotlinAnyType addAny) {
ArrayTypeSignature arrayTypeSignature = typeSignature.asArrayTypeSignature();
if (!arrayTypeSignature.elementSignature().isFieldTypeSignature()) {
return;
}
- KmTypeVisitor kmType = visitor.get();
- kmType.visitClass(NAME + "/Array");
+ KmTypeVisitor kmType = typeVisitor.apply(KmVisitorOption.VISIT_NEW);
+ kmType.visitClass(ClassClassifiers.arrayBinaryName);
+ KotlinTypeProjectionInfo projectionInfo =
+ originalTypeInfo == null ? null : originalTypeInfo.getArgumentOrNull(0);
populateKmTypeFromSignature(
arrayTypeSignature.elementSignature().asFieldTypeSignature(),
- () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+ projectionInfo == null ? null : projectionInfo.typeInfo,
+ (kmVisitorOption) -> {
+ if (kmVisitorOption == KmVisitorOption.VISIT_PARENT) {
+ assert originalTypeInfo.getArguments().size() == 1
+ && originalTypeInfo.getArguments().get(0).isStarProjection();
+ return kmType;
+ } else {
+ // TODO(b/152886451): Variance is only NULL when star projection. If that is the case
+ // we should just apply starProjection.
+ return kmType.visitArgument(
+ flagsOf(),
+ projectionInfo == null || projectionInfo.variance == null
+ ? KmVariance.INVARIANT
+ : projectionInfo.variance);
+ }
+ },
allTypeParameters,
- factory);
+ factory,
+ addAny);
}
private static void populateKmTypeFromClassTypeSignature(
ClassTypeSignature typeSignature,
- Supplier<KmTypeVisitor> visitor,
+ KotlinTypeInfo originalTypeInfo,
+ Function<KmVisitorOption, KmTypeVisitor> typeVisitor,
List<KmTypeParameter> allTypeParameters,
- DexItemFactory factory) {
+ DexItemFactory factory,
+ AddKotlinAnyType addAny) {
// No need to record the trivial argument.
- if (factory.objectType == typeSignature.type()) {
+ if (addAny == AddKotlinAnyType.DISREGARD && factory.objectType == typeSignature.type()) {
return;
}
- KmTypeVisitor kmType = visitor.get();
+ KmTypeVisitor kmType = typeVisitor.apply(KmVisitorOption.VISIT_NEW);
kmType.visitClass(toClassifier(typeSignature.type(), factory));
- for (FieldTypeSignature typeArgument : typeSignature.typeArguments()) {
+ for (int i = 0; i < typeSignature.typeArguments().size(); i++) {
+ FieldTypeSignature typeArgument = typeSignature.typeArguments().get(i);
+ KotlinTypeProjectionInfo projectionInfo =
+ originalTypeInfo == null ? null : originalTypeInfo.getArgumentOrNull(i);
populateKmTypeFromSignature(
typeArgument,
- () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+ projectionInfo == null ? null : projectionInfo.typeInfo,
+ (kmVisitorOption) -> {
+ if (kmVisitorOption == KmVisitorOption.VISIT_PARENT) {
+ assert projectionInfo == null || projectionInfo.isStarProjection();
+ return kmType;
+ } else {
+ // TODO(b/152886451): Variance is only NULL when star projection. If that is the case
+ // we should just apply starProjection.
+ return kmType.visitArgument(
+ flagsOf(),
+ projectionInfo == null || projectionInfo.variance == null
+ ? KmVariance.INVARIANT
+ : projectionInfo.variance);
+ }
+ },
allTypeParameters,
- factory);
+ factory,
+ addAny);
}
}
@@ -121,6 +192,7 @@
* </pre>
*
* @param classTypeParameters
+ * @param originalTypeParameterInfo
* @param parameters
* @param factory
* @param addedFromParameters
@@ -128,6 +200,7 @@
*/
static List<KmTypeParameter> convertFormalTypeParameters(
List<KmTypeParameter> classTypeParameters,
+ List<KotlinTypeParameterInfo> originalTypeParameterInfo,
List<FormalTypeParameter> parameters,
DexItemFactory factory,
Consumer<KmTypeParameter> addedFromParameters) {
@@ -136,20 +209,31 @@
}
ImmutableList.Builder<KmTypeParameter> builder = ImmutableList.builder();
builder.addAll(classTypeParameters);
- int idCounter = classTypeParameters.size();
// Assign type-variables ids to names. All generic signatures has been minified at this point,
// but it may be that type-variables are used before we can see them (is that allowed?).
- for (FormalTypeParameter parameter : parameters) {
+ for (int i = 0; i < parameters.size(); i++) {
+ FormalTypeParameter parameter = parameters.get(i);
+ int flags =
+ originalTypeParameterInfo.size() > i
+ ? originalTypeParameterInfo.get(i).getFlags()
+ : flagsOf();
+ KmVariance variance =
+ originalTypeParameterInfo.size() > i
+ ? originalTypeParameterInfo.get(i).getVariance()
+ : KmVariance.INVARIANT;
KmTypeParameter element =
- new KmTypeParameter(flagsOf(), parameter.getName(), idCounter++, KmVariance.INVARIANT);
+ new KmTypeParameter(
+ flags,
+ parameter.getName(),
+ getNewId(originalTypeParameterInfo, parameter.getName(), i),
+ variance);
builder.add(element);
addedFromParameters.accept(element);
}
- idCounter = 0;
ImmutableList<KmTypeParameter> allTypeParameters = builder.build();
- for (FormalTypeParameter parameter : parameters) {
- KmTypeParameter kmTypeParameter =
- allTypeParameters.get(classTypeParameters.size() + idCounter++);
+ for (int i = 0; i < parameters.size(); i++) {
+ FormalTypeParameter parameter = parameters.get(i);
+ KmTypeParameter kmTypeParameter = allTypeParameters.get(classTypeParameters.size() + i);
visitUpperBound(parameter.getClassBound(), allTypeParameters, kmTypeParameter, factory);
if (parameter.getInterfaceBounds() != null) {
for (FieldTypeSignature interfaceBound : parameter.getInterfaceBounds()) {
@@ -160,6 +244,20 @@
return allTypeParameters;
}
+ // Tries to pick the id from the type-parameter name. If no such exist, compute the highest id and
+ // add the index of the current argument (to ensure unique naming in sequence).
+ private static int getNewId(
+ List<KotlinTypeParameterInfo> typeParameterInfos, String typeVariable, int currentId) {
+ int maxId = -1;
+ for (KotlinTypeParameterInfo typeParameterInfo : typeParameterInfos) {
+ if (typeParameterInfo.getName().equals(typeVariable)) {
+ return typeParameterInfo.getId();
+ }
+ maxId = Math.max(maxId, typeParameterInfo.getId());
+ }
+ return maxId + 1 + currentId;
+ }
+
private static void visitUpperBound(
FieldTypeSignature typeSignature,
List<KmTypeParameter> allTypeParameters,
@@ -169,6 +267,24 @@
return;
}
populateKmTypeFromSignature(
- typeSignature, () -> parameter.visitUpperBound(flagsOf()), allTypeParameters, factory);
+ typeSignature,
+ null,
+ (kmVisitorOption) -> {
+ assert kmVisitorOption == KmVisitorOption.VISIT_NEW;
+ return parameter.visitUpperBound(flagsOf());
+ },
+ allTypeParameters,
+ factory,
+ AddKotlinAnyType.DISREGARD);
+ }
+
+ public static boolean hasEqualClassifier(KmType one, KmType other) {
+ if (one == null && other == null) {
+ return true;
+ }
+ if (one == null || other == null) {
+ return false;
+ }
+ return KotlinClassifierInfo.equals(one.classifier, other.classifier);
}
}
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 130a660..e9bc069 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -4,11 +4,19 @@
package com.android.tools.r8.kotlin;
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.List;
import kotlinx.metadata.KmClassifier;
import kotlinx.metadata.KmType;
import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmTypeVisitor;
// Provides access to Kotlin information about a kotlin type.
public class KotlinTypeInfo {
@@ -45,7 +53,95 @@
return (KmClassifier.TypeAlias) classifier;
}
+ public boolean isClass() {
+ return classifier instanceof KmClassifier.Class;
+ }
+
+ public KmClassifier.Class asClass() {
+ return (KmClassifier.Class) classifier;
+ }
+
+ public boolean isTypeParameter() {
+ return classifier instanceof KmClassifier.TypeParameter;
+ }
+
+ public KmClassifier.TypeParameter asTypeParameter() {
+ return (KmClassifier.TypeParameter) classifier;
+ }
+
+ public boolean isObjectArray() {
+ if (!isClass()) {
+ KmClassifier.Class classifier = (KmClassifier.Class) this.classifier;
+ return classifier.getName().equals(ClassClassifiers.arrayBinaryName) && arguments.size() == 1;
+ }
+ return false;
+ }
+
public List<KotlinTypeProjectionInfo> getArguments() {
return arguments;
}
+
+ public KotlinTypeProjectionInfo getArgumentOrNull(int index) {
+ List<KotlinTypeProjectionInfo> arguments = getArguments();
+ return arguments.size() > index ? getArguments().get(index) : null;
+ }
+
+ public KotlinTypeInfo toRenamed(KotlinMetadataSynthesizer synthesizer) {
+ DexType originalType = getLiveDexTypeFromClassClassifier(synthesizer.appView);
+ if (isClass() && originalType == null) {
+ return null;
+ }
+ KmClassifier renamedClassifier = classifier;
+ if (originalType != null) {
+ String typeClassifier = synthesizer.toRenamedClassifier(originalType);
+ if (typeClassifier != null) {
+ renamedClassifier = new KmClassifier.Class(typeClassifier);
+ }
+ }
+ if (arguments.isEmpty()) {
+ return renamedClassifier == classifier
+ ? this
+ : new KotlinTypeInfo(renamedClassifier, EMPTY_ARGUMENTS);
+ }
+ ImmutableList.Builder<KotlinTypeProjectionInfo> builder = ImmutableList.builder();
+ for (KotlinTypeProjectionInfo argument : arguments) {
+ builder.add(
+ new KotlinTypeProjectionInfo(
+ argument.variance,
+ argument.typeInfo == null ? null : argument.typeInfo.toRenamed(synthesizer)));
+ }
+ return new KotlinTypeInfo(renamedClassifier, builder.build());
+ }
+
+ private DexType getLiveDexTypeFromClassClassifier(AppView<AppInfoWithLiveness> appView) {
+ if (!isClass()) {
+ return null;
+ }
+ String descriptor = DescriptorUtils.getDescriptorFromKotlinClassifier(asClass().getName());
+ DexType type = appView.dexItemFactory().createType(descriptor);
+ if (appView.appInfo().wasPruned(type)) {
+ return null;
+ }
+ return type;
+ }
+
+ public KmType asKmType() {
+ KmType kmType = new KmType(flagsOf());
+ visit(kmType);
+ return kmType;
+ }
+
+ public void visit(KmTypeVisitor visitor) {
+ if (isClass()) {
+ visitor.visitClass(asClass().getName());
+ } else if (isTypeAlias()) {
+ visitor.visitTypeAlias(asTypeAlias().getName());
+ } else {
+ assert isTypeParameter();
+ visitor.visitTypeParameter(asTypeParameter().getId());
+ }
+ for (KotlinTypeProjectionInfo argument : arguments) {
+ argument.visit(visitor);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
new file mode 100644
index 0000000..b9a86d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeParameterInfo.java
@@ -0,0 +1,48 @@
+// 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 kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmVariance;
+
+// Provides access to Kotlin information about a type-parameter.
+public class KotlinTypeParameterInfo {
+
+ private final int flags;
+ private final int id;
+ private final String name;
+ private final KmVariance variance;
+
+ private KotlinTypeParameterInfo(int flags, int id, String name, KmVariance variance) {
+ this.flags = flags;
+ this.id = id;
+ this.name = name;
+ this.variance = variance;
+ }
+
+ static KotlinTypeParameterInfo fromKmTypeParameter(KmTypeParameter kmTypeParameter) {
+ return new KotlinTypeParameterInfo(
+ kmTypeParameter.getFlags(),
+ kmTypeParameter.getId(),
+ kmTypeParameter.getName(),
+ kmTypeParameter.getVariance());
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public KmVariance getVariance() {
+ return variance;
+ }
+}
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 3782bd8..a16c78e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeProjectionInfo.java
@@ -4,7 +4,10 @@
package com.android.tools.r8.kotlin;
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmTypeVisitor;
import kotlinx.metadata.KmVariance;
// Provides access to Kotlin information about the type projection of a type (arguments).
@@ -13,7 +16,7 @@
final KmVariance variance;
final KotlinTypeInfo typeInfo;
- private KotlinTypeProjectionInfo(KmVariance variance, KotlinTypeInfo typeInfo) {
+ KotlinTypeProjectionInfo(KmVariance variance, KotlinTypeInfo typeInfo) {
this.variance = variance;
this.typeInfo = typeInfo;
}
@@ -22,4 +25,18 @@
return new KotlinTypeProjectionInfo(
kmTypeProjection.getVariance(), KotlinTypeInfo.create(kmTypeProjection.getType()));
}
+
+ public boolean isStarProjection() {
+ return variance == null && typeInfo == null;
+ }
+
+ public void visit(KmTypeVisitor visitor) {
+ KmTypeVisitor kmTypeVisitor = visitor.visitArgument(flagsOf(), variance);
+ // TODO(b/152886451): Check if this check should be before visitor.visitArgument(...).
+ if (isStarProjection()) {
+ kmTypeVisitor.visitStarProjection();
+ } else {
+ typeInfo.visit(kmTypeVisitor);
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
index d8d62ae..b2f5a80 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringUtils.java
@@ -173,7 +173,7 @@
}
static boolean isClassNameComparison(InvokeVirtual invoke, DexItemFactory dexItemFactory) {
- return invoke.getInvokedMethod() == dexItemFactory.stringMethods.equals
+ return invoke.getInvokedMethod() == dexItemFactory.stringMembers.equals
&& (isClassNameValue(invoke.getReceiver(), dexItemFactory)
|| isClassNameValue(invoke.inValues().get(1), dexItemFactory));
}
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 da62097..f4c32c6 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -132,7 +132,8 @@
ClassNameMinifier classNameMinifier =
new ClassNameMinifier(
appView,
- new ApplyMappingClassNamingStrategy(appView, mappedNames),
+ new ApplyMappingClassNamingStrategy(
+ appView, mappedNames, seedMapper.getMappedToDescriptorNames()),
// The package naming strategy will actually not be used since all classes and methods
// will be output with identity name if not found in mapping. However, there is a check
// in the ClassNameMinifier that the strategy should produce a "fresh" name so we just
@@ -390,10 +391,13 @@
static class ApplyMappingClassNamingStrategy extends MinificationClassNamingStrategy {
private final Map<DexType, DexString> mappings;
+ private final Set<String> mappedNames;
- ApplyMappingClassNamingStrategy(AppView<?> appView, Map<DexType, DexString> mappings) {
+ ApplyMappingClassNamingStrategy(
+ AppView<?> appView, Map<DexType, DexString> mappings, Set<String> mappedNames) {
super(appView);
this.mappings = mappings;
+ this.mappedNames = mappedNames;
}
@Override
@@ -404,7 +408,16 @@
Predicate<DexString> isUsed) {
assert !mappings.containsKey(type);
assert appView.rootSet().mayBeMinified(type, appView);
- return super.next(type, packagePrefix, state, isUsed);
+ return super.next(
+ type,
+ packagePrefix,
+ state,
+ candidate -> {
+ if (mappedNames.contains(candidate.toString())) {
+ return true;
+ }
+ return isUsed.test(candidate);
+ });
}
@Override
diff --git a/src/main/java/com/android/tools/r8/naming/SeedMapper.java b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
index d7f36da..be69416 100644
--- a/src/main/java/com/android/tools/r8/naming/SeedMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/SeedMapper.java
@@ -20,6 +20,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -37,6 +38,7 @@
static class Builder extends ProguardMap.Builder {
final Map<String, ClassNamingForMapApplier.Builder> map = new HashMap<>();
+ final Set<String> mappedToDescriptorNames = new HashSet<>();
private final Reporter reporter;
private Builder(Reporter reporter) {
@@ -47,9 +49,11 @@
ClassNamingForMapApplier.Builder classNamingBuilder(
String renamedName, String originalName, Position position) {
String originalDescriptor = javaTypeToDescriptor(originalName);
+ String renamedDescriptorName = javaTypeToDescriptor(renamedName);
+ mappedToDescriptorNames.add(renamedDescriptorName);
ClassNamingForMapApplier.Builder classNamingBuilder =
ClassNamingForMapApplier.builder(
- javaTypeToDescriptor(renamedName), originalDescriptor, position, reporter);
+ renamedDescriptorName, originalDescriptor, position, reporter);
if (map.put(originalDescriptor, classNamingBuilder) != null) {
reporter.error(ProguardMapError.duplicateSourceClass(originalName, position));
}
@@ -59,7 +63,7 @@
@Override
SeedMapper build() {
reporter.failIfPendingErrors();
- return new SeedMapper(ImmutableMap.copyOf(map), reporter);
+ return new SeedMapper(ImmutableMap.copyOf(map), mappedToDescriptorNames, reporter);
}
}
@@ -82,15 +86,20 @@
}
private final ImmutableMap<String, ClassNamingForMapApplier> mappings;
+ private final Set<String> mappedToDescriptorNames;
private final Reporter reporter;
- private SeedMapper(Map<String, ClassNamingForMapApplier.Builder> mappings, Reporter reporter) {
+ private SeedMapper(
+ Map<String, ClassNamingForMapApplier.Builder> mappings,
+ Set<String> mappedToDescriptorNames,
+ Reporter reporter) {
this.reporter = reporter;
ImmutableMap.Builder<String, ClassNamingForMapApplier> builder = ImmutableMap.builder();
for(Map.Entry<String, ClassNamingForMapApplier.Builder> entry : mappings.entrySet()) {
builder.put(entry.getKey(), entry.getValue().build());
}
this.mappings = builder.build();
+ this.mappedToDescriptorNames = mappedToDescriptorNames;
verifyMappingsAreConflictFree();
}
@@ -140,6 +149,10 @@
return mappings.keySet();
}
+ public Set<String> getMappedToDescriptorNames() {
+ return mappedToDescriptorNames;
+ }
+
public ClassNamingForMapApplier getMapping(String key) {
return mappings.get(key);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 4816759..a52d044 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -147,7 +147,7 @@
return true;
}
- // For private static methods we can just relax the access to private, since
+ // For private static methods we can just relax the access to public, since
// even though JLS prevents from declaring static method in derived class if
// an instance method with same signature exists in superclass, JVM actually
// does not take into account access of the static methods.
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 78136a9..f847e9c 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -43,10 +43,10 @@
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.utils.CollectionUtils;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.PredicateSet;
import com.android.tools.r8.utils.Visibility;
-import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ImmutableSortedSet.Builder;
@@ -546,6 +546,31 @@
return definition;
}
+ private int largestInputCfVersion = -1;
+
+ public boolean canUseConstClassInstructions(InternalOptions options) {
+ if (!options.isGeneratingClassFiles()) {
+ return true;
+ }
+ if (largestInputCfVersion == -1) {
+ computeLargestCfVersion();
+ }
+ return options.canUseConstClassInstructions(largestInputCfVersion);
+ }
+
+ private synchronized void computeLargestCfVersion() {
+ if (largestInputCfVersion != -1) {
+ return;
+ }
+ for (DexProgramClass clazz : classes()) {
+ // Skip synthetic classes which may not have a specified version.
+ if (clazz.hasClassFileVersion()) {
+ largestInputCfVersion = Math.max(largestInputCfVersion, clazz.getInitialClassFileVersion());
+ }
+ }
+ assert largestInputCfVersion != -1;
+ }
+
public boolean isLiveProgramClass(DexProgramClass clazz) {
return liveTypes.contains(clazz.type);
}
@@ -1313,19 +1338,8 @@
DexType type,
Consumer<DexProgramClass> subTypeConsumer,
Consumer<LambdaDescriptor> callSiteConsumer) {
- WorkList<DexType> workList = WorkList.newIdentityWorkList();
- workList.addIfNotSeen(type);
- while (workList.hasNext()) {
- DexType subType = workList.next();
- DexProgramClass clazz = definitionForProgramType(subType);
- workList.addIfNotSeen(allImmediateSubtypes(subType));
- if (clazz == null) {
- continue;
- }
- if (isInstantiatedOrPinned(clazz)) {
- subTypeConsumer.accept(clazz);
- }
- }
+ objectAllocationInfoCollection.forEachInstantiatedSubType(
+ type, subTypeConsumer, callSiteConsumer, this);
}
public void forEachInstantiatedSubTypeInChain(
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 f636d05..176f908 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -52,6 +52,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.DirectMappedDexApplication.Builder;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.EnumValueInfoMapCollection;
import com.android.tools.r8.graph.FieldAccessInfoCollectionImpl;
import com.android.tools.r8.graph.FieldAccessInfoImpl;
@@ -1443,6 +1444,15 @@
recordTypeReference(innerClassAttribute.getInner());
recordTypeReference(innerClassAttribute.getOuter());
}
+ EnclosingMethodAttribute enclosingMethodAttribute = holder.getEnclosingMethod();
+ if (enclosingMethodAttribute != null) {
+ DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
+ if (enclosingMethod != null) {
+ recordMethodReference(enclosingMethod);
+ } else {
+ recordTypeReference(enclosingMethodAttribute.getEnclosingClass());
+ }
+ }
if (Log.ENABLED) {
Log.verbose(getClass(), "Type `%s` has become live.", holder.type);
@@ -2726,18 +2736,11 @@
DexEncodedMethod context = lambdaClassAndContext.getSecond();
DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
additions.addInstantiatedClass(programClass, context, lambdaClass.addToMainDexList.get());
-
- // Mark all methods on the desugared lambda classes as live.
- for (DexEncodedMethod method : programClass.methods()) {
- additions.addLiveMethod(new ProgramMethod(programClass, method));
- }
-
- // Ensure accessors if needed and mark them live too.
- DexEncodedMethod accessor = lambdaClass.target.ensureAccessibilityIfNeeded(false);
- if (accessor != null) {
- DexProgramClass clazz = getProgramClassOrNull(accessor.holder());
- additions.addLiveMethod(new ProgramMethod(clazz, accessor));
- }
+ // Mark the instance constructor targeted and live.
+ DexEncodedMethod constructor = programClass.lookupDirectMethod(lambdaClass.constructor);
+ KeepReason reason = KeepReason.instantiatedIn(context);
+ markMethodAsTargeted(programClass, constructor, reason);
+ markDirectStaticOrConstructorMethodAsLive(programClass, constructor, reason);
}
// Rewrite all of the invoke-dynamic instructions to lambda class instantiations.
@@ -2774,6 +2777,12 @@
}
private AppInfoWithLiveness createAppInfo(AppInfoWithSubtyping appInfo) {
+ // Once all tracing is done, we generate accessor methods for lambdas.
+ // These are assumed to be simple forwarding or access flag updates, thus no further tracing
+ // is needed. These cannot be generated as part of lambda synthesis as changing a direct method
+ // to a static method will invalidate the reachable method sets for tracing methods.
+ ensureLambdaAccessibility();
+
// Compute the set of dead proto types.
deadProtoTypeCandidates.removeIf(this::isTypeLive);
@@ -2866,6 +2875,36 @@
return appInfoWithLiveness;
}
+ private void ensureLambdaAccessibility() {
+ if (lambdaRewriter == null) {
+ return;
+ }
+ lambdaRewriter
+ .getKnownLambdaClasses()
+ .forEach(
+ (type, lambda) -> {
+ DexProgramClass synthesizedClass = getProgramClassOrNull(type);
+ assert synthesizedClass != null;
+ assert liveTypes.contains(synthesizedClass);
+ if (synthesizedClass == null) {
+ return;
+ }
+ DexMethod method = lambda.descriptor.getMainMethod();
+ if (!liveMethods.contains(synthesizedClass.lookupMethod(method))) {
+ return;
+ }
+ DexEncodedMethod accessor = lambda.target.ensureAccessibilityIfNeeded(false);
+ if (accessor == null) {
+ return;
+ }
+ DexProgramClass accessorClass = getProgramClassOrNull(accessor.holder());
+ assert accessorClass != null;
+ if (accessorClass != null) {
+ liveMethods.add(accessorClass, accessor, graphReporter.fakeReportShouldNotBeUsed());
+ }
+ });
+ }
+
private boolean verifyReferences(DexApplication app) {
WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
for (DexProgramClass clazz : liveTypes.getItems()) {
@@ -3668,12 +3707,16 @@
if (clazz != null && clazz.isInterface()) {
// 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.
pinnedItems.add(clazz.type);
+ 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()) {
pinnedItems.add(virtualMethod.method);
+ markVirtualMethodAsReachable(virtualMethod.method, true, null, reason);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 2276790..a47684f 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -86,14 +86,9 @@
}
/** Get or open the zip output stream. */
- private synchronized ZipOutputStream getStream(DiagnosticsHandler handler) {
+ private synchronized ZipOutputStream getStream() throws IOException {
assert !closed;
- try {
- getStreamRaw();
- } catch (IOException e) {
- handler.error(new ExceptionDiagnostic(e, origin));
- }
- return stream;
+ return getStreamRaw();
}
private void handleIOException(IOException e, DiagnosticsHandler handler) {
@@ -117,9 +112,9 @@
}
ZipEntry entry = new ZipEntry(name);
entry.setTime(0);
- ZipOutputStream zip = getStream(handler);
synchronized (this) {
try {
+ ZipOutputStream zip = getStream();
zip.putNextEntry(entry);
zip.closeEntry();
} catch (IOException e) {
@@ -150,7 +145,7 @@
private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
try {
- ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.DEFLATED);
+ ZipUtils.writeToZipStream(getStream(), name, content, ZipEntry.DEFLATED);
} catch (IOException e) {
handleIOException(e, handler);
}
diff --git a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
index 271bd70..21a5123 100644
--- a/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
+++ b/src/main/java/com/android/tools/r8/utils/CompileDumpCompatR8.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command.Builder;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -46,6 +47,7 @@
boolean isCompatMode = false;
OutputMode outputMode = OutputMode.DexIndexed;
Path outputPath = null;
+ Path pgMapOutput = null;
CompilationMode compilationMode = CompilationMode.RELEASE;
List<Path> program = new ArrayList<>();
List<Path> library = new ArrayList<>();
@@ -107,6 +109,11 @@
config.add(Paths.get(operand));
break;
}
+ case "--pg-map-output":
+ {
+ pgMapOutput = Paths.get(operand);
+ break;
+ }
default:
throw new IllegalArgumentException("Unimplemented option: " + option);
}
@@ -114,7 +121,7 @@
program.add(Paths.get(option));
}
}
- R8.run(
+ Builder builder =
new CompatProguardCommandBuilder(isCompatMode)
.addProgramFiles(program)
.addLibraryFiles(library)
@@ -122,7 +129,10 @@
.addProguardConfigurationFiles(config)
.setOutput(outputPath, outputMode)
.setMode(compilationMode)
- .setMinApiLevel(minApi)
- .build());
+ .setMinApiLevel(minApi);
+ if (pgMapOutput != null) {
+ builder.setProguardMapOutputPath(pgMapOutput);
+ }
+ R8.run(builder.build());
}
}
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 6aca533..de25a3b 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -17,8 +17,6 @@
import java.io.File;
import java.nio.file.Path;
import java.util.Map;
-import kotlinx.metadata.KmType;
-import kotlinx.metadata.KmTypeVisitor;
public class DescriptorUtils {
@@ -379,36 +377,6 @@
}
/**
- * Get a fully qualified name from a classifier in Kotlin metadata.
- * @param kmType where classifier contains Kotlin internal name, like "org/foo/bar/Baz.Nested"
- * @return a class descriptor like "Lorg/foo/bar/Baz$Nested;"
- */
- public static String getDescriptorFromKmType(KmType kmType) {
- if (kmType == null) {
- return null;
- }
- Box<String> descriptor = new Box<>(null);
- kmType.accept(new KmTypeVisitor() {
- @Override
- public void visitClass(String name) {
- // TODO(b/151195430): Remove this if metadata lib is resilient to namespace relocation.
- // See b/70169921#comment25 for more details.
- assert descriptor.get() == null : descriptor.get();
- descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
- }
-
- @Override
- public void visitTypeAlias(String name) {
- // TODO(b/151195430): Remove this if metadata lib is resilient to namespace relocation.
- // See b/70169921#comment25 for more details.
- assert descriptor.get() == null : descriptor.get();
- descriptor.set(getDescriptorFromKotlinClassifier(backwardRelocatedName(name)));
- }
- });
- return descriptor.get();
- }
-
- /**
* Get unqualified class name from its binary name.
*
* @param classBinaryName a class binary name i.e. "java/lang/Object" or "a/b/C$Inner"
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 ad290b6..51c6934 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -773,7 +773,7 @@
private String messageWarningMissingNestHost(DexClass compiledClass) {
return messageErrorMissingNestHost(compiledClass)
- + "Class"
+ + "Class "
+ compiledClass.type.getName()
+ " is considered as not being part of any nest.";
}
@@ -1202,6 +1202,16 @@
return minApiLevel >= level.getLevel();
}
+ public boolean canUseConstClassInstructions(int cfVersion) {
+ assert isGeneratingClassFiles();
+ return cfVersion >= requiredCfVersionForConstClassInstructions();
+ }
+
+ public int requiredCfVersionForConstClassInstructions() {
+ assert isGeneratingClassFiles();
+ return Opcodes.V1_5;
+ }
+
public boolean canUseInvokePolymorphicOnVarHandle() {
return isGeneratingClassFiles() || hasMinApi(AndroidApiLevel.P);
}
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index 4a94ac1..42cf72a 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -98,8 +98,7 @@
fileNotNamedKt -> {
try {
// The Kotlin compiler does not require particular naming of files except for
- // the extension,
- // so just create a file called source.kt in a new directory.
+ // the extension, so just create a file called source.kt in a new directory.
Path fileNamedKt = temp.newFolder().toPath().resolve("source.kt");
Files.copy(fileNotNamedKt, fileNamedKt);
return fileNamedKt;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index b8256fe..a642002 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 116, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -142,7 +142,7 @@
.withOptionConsumer(opts -> opts.enableClassInlining = false)
.withBuilderTransformation(
b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
- .withDexCheck(inspector -> checkLambdaCount(inspector, 116, "lambdadesugaring"))
+ .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
.run();
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
new file mode 100644
index 0000000..a083cdd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/GetClassLdcClassTest.java
@@ -0,0 +1,133 @@
+// 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.cf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class GetClassLdcClassTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines(Runner.class.getName());
+
+ private final TestParameters parameters;
+ private final int version;
+
+ @Parameterized.Parameters(name = "{0}, cf:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ new Integer[] {Opcodes.V1_4, Opcodes.V1_5});
+ }
+
+ public GetClassLdcClassTest(TestParameters parameters, int version) {
+ this.parameters = parameters;
+ this.version = version;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ // Check the program works with the code as-is and the version downgraded.
+ testForRuntime(parameters)
+ .addProgramClassFileData(getDowngradedClass(Runner.class))
+ .addProgramClassFileData(getDowngradedClass(TestClass.class))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ inspector -> {
+ if (parameters.isCfRuntime()) {
+ checkVersion(inspector, TestClass.class, version);
+ checkVersion(inspector, Runner.class, version);
+ }
+ });
+ }
+
+ @Test
+ public void testNoVersionUpgrade() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getDowngradedClass(Runner.class))
+ .addProgramClassFileData(getDowngradedClass(TestClass.class))
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassAndMembersRules(Runner.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ inspector -> {
+ if (parameters.isCfRuntime()) {
+ checkVersion(inspector, TestClass.class, version);
+ checkVersion(inspector, Runner.class, version);
+ }
+ });
+ }
+
+ @Test
+ public void testWithVersionUpgrade() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getDowngradedClass(Runner.class))
+ // Here the main class is not downgraded, thus the output may upgrade to that version.
+ .addProgramClasses(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassAndMembersRules(Runner.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(
+ inspector -> {
+ if (parameters.isCfRuntime()) {
+ // We are assuming the runtimes we are testing are post CF SE 1.4 (version 48).
+ int cfVersionForRuntime = getVersion(inspector, TestClass.class);
+ assertNotEquals(Opcodes.V1_4, cfVersionForRuntime);
+ // Check that the downgraded class has been bumped to at least SE 1.5 (version 49).
+ int cfVersionAfterUpgrade = getVersion(inspector, Runner.class);
+ assertTrue(cfVersionAfterUpgrade >= Opcodes.V1_5);
+ }
+ // Check that the method uses a const class instruction.
+ assertTrue(
+ inspector
+ .clazz(Runner.class)
+ .uniqueMethodWithName("run")
+ .streamInstructions()
+ .anyMatch(i -> i.isConstClass(Runner.class.getTypeName())));
+ });
+ }
+
+ private static int getVersion(CodeInspector inspector, Class<?> clazz) {
+ return inspector.clazz(clazz).getDexClass().asProgramClass().getInitialClassFileVersion();
+ }
+
+ private static void checkVersion(CodeInspector inspector, Class<?> clazz, int version) {
+ assertEquals(version, getVersion(inspector, clazz));
+ }
+
+ private byte[] getDowngradedClass(Class<?> clazz) throws IOException {
+ return transformer(clazz).setVersion(version).transform();
+ }
+
+ static class Runner {
+
+ public void run() {
+ System.out.println(getClass().getName());
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new Runner().run();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java b/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
index 03f5803..ba16ddc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/InterfaceWithProxyTest.java
@@ -18,6 +18,8 @@
@RunWith(Parameterized.class)
public class InterfaceWithProxyTest extends TestBase {
+ private static final String EXPECTED = StringUtils.lines("Hello world!");
+
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
@@ -30,8 +32,15 @@
}
@Test
- public void test() throws Exception {
- String expectedOutput = StringUtils.lines("Hello world!");
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(InterfaceWithProxyTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addInnerClasses(InterfaceWithProxyTest.class)
.addKeepMainRule(TestClass.class)
@@ -39,7 +48,7 @@
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutput(expectedOutput);
+ .assertSuccessWithOutput(EXPECTED);
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
index e209e44..7a97972 100644
--- a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -6,19 +6,21 @@
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.Assume.assumeTrue;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.Disassemble;
import com.android.tools.r8.Disassemble.DisassembleCommand;
import com.android.tools.r8.JvmTestBuilder;
import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debug.DebugTestBase;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
@@ -28,57 +30,85 @@
import java.util.Collections;
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 DefaultLambdaWithSelfReferenceTestRunner extends DebugTestBase {
- final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
- final String EXPECTED = StringUtils.lines("stateful(stateless)");
+ private static final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
+ private static final String EXPECTED = StringUtils.lines("stateful(stateless)");
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public DefaultLambdaWithSelfReferenceTestRunner(TestParameters parameters) {
+ this.parameters = parameters;
+ }
private void runDebugger(DebugTestConfig config) throws Throwable {
MethodReference main = Reference.methodFromMethod(CLASS.getMethod("main", String[].class));
- Command checkThis = conditional((state) ->
- state.isCfRuntime()
- ? Collections.singletonList(checkLocal("this"))
- : ImmutableList.of(
- checkNoLocal("this"),
- checkLocal("_this")));
+ Command checkThisLambda =
+ conditional(
+ (state) ->
+ parameters.isCfRuntime()
+ ? Collections.singletonList(checkLocal("this"))
+ : ImmutableList.of(checkNoLocal("this"), checkLocal("_this")));
- runDebugTest(config, CLASS,
+ Command checkThisDefaultMethod =
+ conditional(
+ (state) ->
+ parameters.canUseDefaultAndStaticInterfaceMethods()
+ ? Collections.singletonList(checkLocal("this"))
+ : ImmutableList.of(checkNoLocal("this"), checkLocal("_this")));
+
+ runDebugTest(
+ config,
+ CLASS,
breakpoint(main, 26),
run(),
checkLine(26),
stepInto(INTELLIJ_FILTER),
checkLine(16),
// When desugaring, the InterfaceProcessor makes this static on the companion class.
- checkThis,
+ checkThisDefaultMethod,
breakpoint(main, 27),
run(),
checkLine(27),
stepInto(INTELLIJ_FILTER),
checkLine(17),
// When desugaring, the LambdaClass will change this to a static (later moved to companion).
- checkThis,
+ checkThisLambda,
run());
}
@Test
public void testJvm() throws Throwable {
+ assumeTrue(parameters.isCfRuntime());
JvmTestBuilder builder = testForJvm().addTestClasspath();
- builder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ builder.run(parameters.getRuntime(), CLASS).assertSuccessWithOutput(EXPECTED);
runDebugger(builder.debugConfig());
}
@Test
- public void testR8Cf() throws Throwable {
- R8TestCompileResult compileResult = testForR8(Backend.CF)
- .addProgramClassesAndInnerClasses(CLASS)
- .noMinification()
- .noTreeShaking()
- .debug()
- .compile();
+ public void testR8() throws Throwable {
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramClassesAndInnerClasses(CLASS)
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .noTreeShaking()
+ .addKeepAllAttributes()
+ .debug()
+ .compile()
+ .assertNoMessages();
compileResult
- // TODO(b/123506120): Add .assertNoMessages()
- .run(CLASS)
+ .run(parameters.getRuntime(), CLASS)
.assertSuccessWithOutput(EXPECTED)
.inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
runDebugger(compileResult.debugConfig());
@@ -86,14 +116,15 @@
@Test
public void testD8() throws Throwable {
+ assumeTrue(parameters.isDexRuntime());
Path out1 = temp.newFolder().toPath().resolve("out1.zip");
testForD8()
.addProgramClassesAndInnerClasses(CLASS)
- .setMinApi(AndroidApiLevel.K)
+ .setMinApi(parameters.getApiLevel())
.compile()
- // TODO(b/123506120): Add .assertNoMessages()
+ .assertNoMessages()
.writeToZip(out1)
- .run(CLASS)
+ .run(parameters.getRuntime(), CLASS)
.assertSuccessWithOutput(EXPECTED);
Path outPerClassDir = temp.newFolder().toPath();
@@ -109,9 +140,9 @@
.addProgramClasses(CLASS)
.addClasspathFiles(ToolHelper.getClassPathForTests())
.setIntermediate(true)
- .setMinApi(AndroidApiLevel.K)
+ .setMinApi(parameters.getApiLevel())
.compile()
- // TODO(b/123506120): Add .assertNoMessages()
+ .assertNoMessages()
.writeToZip(mainOut);
}
for (Path innerClass : innerClasses) {
@@ -121,21 +152,20 @@
.addProgramFiles(innerClass)
.addClasspathFiles(ToolHelper.getClassPathForTests())
.setIntermediate(true)
- .setMinApi(AndroidApiLevel.K)
+ .setMinApi(parameters.getApiLevel())
.compile()
- // TODO(b/123506120): Add .assertNoMessages()
+ .assertNoMessages()
.writeToZip(out);
}
Path out2 = temp.newFolder().toPath().resolve("out2.zip");
- D8TestCompileResult compiledResult = testForD8()
- .addProgramFiles(outs)
- .compile();
+ D8TestCompileResult compiledResult =
+ testForD8().addProgramFiles(outs).setMinApi(parameters.getApiLevel()).compile();
compiledResult
- // TODO(b/123506120): Add .assertNoMessages()
+ .assertNoMessages()
.writeToZip(out2)
- .run(CLASS)
+ .run(parameters.getRuntime(), CLASS)
.assertSuccessWithOutput(EXPECTED);
runDebugger(compiledResult.debugConfig());
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.java
new file mode 100644
index 0000000..0c778d0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultMethodWithAccessTest.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.desugar;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+// This is a reproduction of b/153042496 in a java-only setting.
+
+@RunWith(Parameterized.class)
+public class DefaultMethodWithAccessTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final boolean implementI0I1;
+
+ @Parameters(name = "{0}, implementI0I1: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ public DefaultMethodWithAccessTest(TestParameters parameters, boolean implementI0I1) {
+ this.parameters = parameters;
+ this.implementI0I1 = implementI0I1;
+ }
+
+ @Test
+ public void testRuntime() throws ExecutionException, CompilationFailedException, IOException {
+ testForRuntime(parameters)
+ .addProgramClasses(I0.class, I1.class, Main.class, Impl.class)
+ .addProgramClassFileData(transformI2AccessToInvokeSpecial())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ private byte[] transformI2AccessToInvokeSpecial() throws IOException {
+ ClassFileTransformer classFileTransformer =
+ transformer(I2.class)
+ .transformMethodInsnInMethod(
+ "access",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ continuation.apply(
+ name.equals("print") ? Opcodes.INVOKESPECIAL : opcode,
+ owner,
+ name,
+ descriptor,
+ isInterface);
+ });
+ if (implementI0I1) {
+ classFileTransformer.setImplements(I0.class, I1.class);
+ }
+ return classFileTransformer.transform();
+ }
+
+ public interface I0 {
+ void print();
+ }
+
+ public interface I1 {
+ default void print() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ public interface I2 extends /* I0, */ I1 {
+
+ static void access(I2 i2) {
+ /* invoke-special */ i2.print();
+ }
+ }
+
+ public static class Impl implements I2 {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ testPrint(new Impl());
+ }
+
+ public static void testPrint(I2 i) {
+ I2.access(i);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java
new file mode 100644
index 0000000..564f831
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/PrivateMethodsInInterfaceTest.java
@@ -0,0 +1,102 @@
+// 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.desugar;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PrivateMethodsInInterfaceTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public PrivateMethodsInInterfaceTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime()
+ throws NoSuchMethodException, IOException, CompilationFailedException, ExecutionException {
+ testForRuntime(parameters)
+ .addProgramClasses(SubI.class, Impl.class, Main.class)
+ .addProgramClassFileData(transformIToPrivate())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!", "Hello World!", "Hello World!");
+ }
+
+ private byte[] transformIToPrivate() throws NoSuchMethodException, IOException {
+ return transformer(I.class)
+ .setPrivate(I.class.getDeclaredMethod("bar"))
+ .setPrivate(I.class.getDeclaredMethod("baz", I.class))
+ .transformMethodInsnInMethod(
+ "foo",
+ ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+ continuation.apply(
+ name.equals("bar") ? Opcodes.INVOKESPECIAL : opcode,
+ owner,
+ name,
+ descriptor,
+ isInterface);
+ }))
+ .transformMethodInsnInMethod(
+ "baz",
+ ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+ continuation.apply(
+ name.equals("bar") ? Opcodes.INVOKESPECIAL : opcode,
+ owner,
+ name,
+ descriptor,
+ isInterface);
+ }))
+ .transform();
+ }
+
+ public interface I {
+
+ default void foo() {
+ bar();
+ I.qux(this);
+ }
+
+ /* private */ default void bar() {
+ System.out.println("Hello World!");
+ }
+
+ /* private */ static void baz(I i) {
+ i.bar();
+ }
+
+ static void qux(I i) {
+ baz(i);
+ }
+ }
+
+ public interface SubI extends I {}
+
+ public static class Impl implements SubI {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Impl impl = new Impl();
+ impl.foo();
+ I.qux(impl);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java
index 13a4564..e92fcc7 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java
@@ -21,8 +21,8 @@
public ByteBackportTest(TestParameters parameters) {
super(parameters, Byte.class, Main.class);
- registerTarget(AndroidApiLevel.O, 16);
- registerTarget(AndroidApiLevel.N, 8);
+ registerTarget(AndroidApiLevel.O, 17);
+ registerTarget(AndroidApiLevel.N, 9);
registerTarget(AndroidApiLevel.K, 7);
}
@@ -34,10 +34,13 @@
testToUnsignedLong();
}
+ @SuppressWarnings("ResultOfMethodCallIgnored")
private static void testHashCode() {
for (int i = Byte.MIN_VALUE; i < Byte.MAX_VALUE; i++) {
assertEquals(i, Byte.hashCode((byte) i));
}
+ // Test unused invoke.
+ Byte.hashCode((byte) 1);
}
private static void testCompare() {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
new file mode 100644
index 0000000..5f05a31
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingSideEffectClInitTest.java
@@ -0,0 +1,77 @@
+// 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.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 EnumUnboxingSideEffectClInitTest extends EnumUnboxingTestBase {
+ private static final Class<?> ENUM_CLASS = MyEnum.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 EnumUnboxingSideEffectClInitTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> classToTest = MainEnum.class;
+ testForR8(parameters.getBackend())
+ .addInnerClasses(EnumUnboxingSideEffectClInitTest.class)
+ .addKeepMainRule(classToTest)
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .enableNeverClassInliningAnnotations()
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> {
+ // The snap keep rule forces to keep the static MainEnum#e field, so the enum
+ // cannot be unboxed anymore.
+ if (enumKeepRules.toString().equals("snap")) {
+ assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m);
+ } else {
+ assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m);
+ }
+ })
+ .run(parameters.getRuntime(), classToTest)
+ .assertSuccessWithOutputLines("0");
+ }
+
+ @NeverClassInline
+ enum MyEnum {
+ A,
+ B
+ }
+
+ @NeverClassInline
+ enum MainEnum {
+ INSTANCE;
+ // The clinit of this enum needs to be reprocessed by the enum unboxer to rewrite MyEnum.a
+ // and the static put instruction to the new field.
+ static MyEnum e = System.currentTimeMillis() > 0 ? MyEnum.A : MyEnum.B;
+
+ public static void main(String[] args) {
+ System.out.println(e.ordinal());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
new file mode 100644
index 0000000..e2dcc4c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingFailureTest.java
@@ -0,0 +1,63 @@
+// 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.TestParameters;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ValueOfEnumUnboxingFailureTest extends EnumUnboxingTestBase {
+
+ private final TestParameters parameters;
+ private final boolean enumValueOptimization;
+ private final KeepRule enumKeepRules;
+
+ @Parameterized.Parameters(name = "{0} valueOpt: {1} keep: {2}")
+ public static List<Object[]> data() {
+ return enumUnboxingTestParameters();
+ }
+
+ public ValueOfEnumUnboxingFailureTest(
+ TestParameters parameters, boolean enumValueOptimization, KeepRule enumKeepRules) {
+ this.parameters = parameters;
+ this.enumValueOptimization = enumValueOptimization;
+ this.enumKeepRules = enumKeepRules;
+ }
+
+ @Test
+ public void testEnumUnboxing() throws Exception {
+ Class<?> success = Main.class;
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ValueOfEnumUnboxingFailureTest.class)
+ .addKeepMainRule(success)
+ .enableNeverClassInliningAnnotations()
+ .addKeepRules(enumKeepRules.getKeepRule())
+ .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
+ .allowDiagnosticInfoMessages()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspectDiagnosticMessages(
+ m -> assertEnumIsBoxed(success.getDeclaredClasses()[0], success.getSimpleName(), m))
+ .run(parameters.getRuntime(), success)
+ .assertSuccessWithOutput("VALUE1");
+ }
+
+ static class Main {
+
+ @NeverClassInline
+ enum Enum {
+ VALUE1,
+ VALUE2
+ }
+
+ public static void main(String[] args) {
+ System.out.print(java.lang.Enum.valueOf(Enum.class, "VALUE1"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
index b4e1ef6..37f31c9 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/ValueOfEnumUnboxingTest.java
@@ -76,7 +76,7 @@
System.out.println(Enum.valueOf(EnumValueOf.MyEnum.class, "B").ordinal());
System.out.println(1);
try {
- Enum.valueOf(EnumValueOf.MyEnum.class, "C");
+ iae();
} catch (IllegalArgumentException argException) {
System.out.println(argException.getMessage());
System.out.println(
@@ -84,11 +84,20 @@
+ " com.android.tools.r8.enumunboxing.ValueOfEnumUnboxingTest.EnumValueOf.MyEnum.C");
}
try {
- Enum.valueOf(EnumValueOf.MyEnum.class, null);
+ npe();
} catch (NullPointerException npe) {
System.out.println(npe.getMessage());
System.out.println("Name is null");
}
}
+
+ @SuppressWarnings("ConstantConditions")
+ private static void npe() {
+ Enum.valueOf(MyEnum.class, null);
+ }
+
+ private static void iae() {
+ Enum.valueOf(MyEnum.class, "C");
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
index 4d39642..088b075 100644
--- a/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/graph/GenericSignatureTest.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.graph.GenericSignature.Parser;
import com.android.tools.r8.graph.GenericSignature.ReturnType;
import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
import com.android.tools.r8.graph.GenericSignatureTestClassA.I;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.AndroidApp;
@@ -31,9 +32,9 @@
import com.android.tools.r8.utils.codeinspector.FieldSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.List;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -50,7 +51,6 @@
public GenericSignatureTest(TestParameters parameters) {}
@Test
- @Ignore("b/152709234")
public void test() throws Exception {
AndroidApp app =
testForD8()
@@ -240,6 +240,9 @@
assertEquals(1, methodTypeSignature.typeSignatures.size());
parameterSignature = methodTypeSignature.getParameterTypeSignature(0);
check_supplier(factory, a, y, zz, parameterSignature);
+
+ // check_A_Y_foo for star, negative and positive wildcards
+ check_A_Y_foo_bar_baz(y, appView);
}
private void check_A_Y(ClassSubject a, ClassSubject y, ClassTypeSignature signature) {
@@ -270,6 +273,47 @@
assertEquals("TT", typeArgument.asTypeVariableSignature().typeVariable);
}
+ private void check_A_Y_foo_bar_baz(ClassSubject y, AppView<AppInfoWithLiveness> appView) {
+ checkMethodWildCard(y.uniqueMethodWithName("foo"), appView, WildcardIndicator.POSITIVE);
+ checkMethodWildCard(y.uniqueMethodWithName("bar"), appView, WildcardIndicator.NEGATIVE);
+ // Check for star
+ checkFieldTypeSignature(
+ y.uniqueMethodWithName("baz"),
+ appView,
+ typeSignature -> {
+ assertTrue(typeSignature.isStar());
+ });
+ }
+
+ private void checkMethodWildCard(
+ MethodSubject methodSubject,
+ AppView<AppInfoWithLiveness> appView,
+ WildcardIndicator indicator) {
+ checkFieldTypeSignature(
+ methodSubject,
+ appView,
+ typeSignature -> {
+ assertTrue(typeSignature.isTypeVariableSignature());
+ assertEquals(indicator, typeSignature.getWildcardIndicator());
+ });
+ }
+
+ private void checkFieldTypeSignature(
+ MethodSubject methodSubject,
+ AppView<AppInfoWithLiveness> appView,
+ Consumer<FieldTypeSignature> fieldTypeConsumer) {
+ MethodTypeSignature methodTypeSignature =
+ Parser.toMethodTypeSignature(methodSubject.getMethod(), appView);
+ TypeSignature typeSignature = methodTypeSignature.returnType.typeSignature;
+ FieldTypeSignature fieldTypeSignature = typeSignature.asFieldTypeSignature();
+ assertTrue(fieldTypeSignature.isClassTypeSignature());
+ ClassTypeSignature classTypeSignature = fieldTypeSignature.asClassTypeSignature();
+ assertFalse(classTypeSignature.isArgument());
+ assertEquals(1, classTypeSignature.typeArguments.size());
+ FieldTypeSignature typeArgument = classTypeSignature.typeArguments.get(0);
+ fieldTypeConsumer.accept(typeArgument);
+ }
+
private void check_supplier(
DexItemFactory factory,
ClassSubject a,
@@ -308,7 +352,7 @@
class ZZ<TT> extends YY {
public YY yy;
- <R extends I> YY newYY(GenericSignatureTestClassB... bs) {
+ <R extends Y & I> YY newYY(GenericSignatureTestClassB... bs) {
return new YY();
}
@@ -330,6 +374,18 @@
ZZ<T> zz() {
return new ZZ<T>();
}
+
+ List<? extends T> foo() {
+ return null;
+ }
+
+ List<? super T> bar() {
+ return null;
+ }
+
+ List<?> baz() {
+ return null;
+ }
}
class Z extends Y {}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
index 971818b..9644e0d 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/StringSwitchConversionFromIfTest.java
@@ -123,7 +123,7 @@
Instruction instruction, DexItemFactory dexItemFactory) {
// Intentionally using toSourceString() because `instruction.getInvokedMethod()` belongs to
// another factory than the given `dexItemFactory`.
- String signature = dexItemFactory.stringMethods.hashCode.toSourceString();
+ String signature = dexItemFactory.stringMembers.hashCode.toSourceString();
return instruction.isInvokeVirtual()
&& instruction.asInvokeVirtual().getInvokedMethod().toSourceString().equals(signature);
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.java
new file mode 100644
index 0000000..c04f9b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/CallSiteOptimizationWithLambdaTargetTest.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.optimize.callsites;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CallSiteOptimizationWithLambdaTargetTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public CallSiteOptimizationWithLambdaTargetTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(CallSiteOptimizationWithLambdaTargetTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("true", "false");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new A().m(new Object());
+ get().m(null);
+ }
+
+ static I get() {
+ return System.currentTimeMillis() >= 0 ? new A() : System.out::println;
+ }
+ }
+
+ interface I {
+
+ void m(Object o);
+ }
+
+ @NeverClassInline
+ static class A implements I {
+
+ @NeverInline
+ @Override
+ public void m(Object o) {
+ System.out.println(o != null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
index 1e323fb..589fa3f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EnumCanonicalizationTest.java
@@ -68,8 +68,7 @@
MethodSubject mainMethodSubject = classSubject.mainMethod();
assertThat(mainMethodSubject, isPresent());
assertEquals(
- // No canonicalization when generating class files.
- parameters.isCfRuntime() ? 3 : 1,
+ 1,
mainMethodSubject
.streamInstructions()
.filter(InstructionSubject::isStaticGet)
@@ -77,7 +76,7 @@
.filter(enumFieldSubject.getField().field::equals)
.count());
assertEquals(
- 3,
+ 1,
mainMethodSubject
.streamInstructions()
.filter(InstructionSubject::isStaticGet)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
index fe3d79a..2b910f5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantfieldloadelimination/RedundantFinalStaticFieldLoadAfterStoreTest.java
@@ -65,9 +65,8 @@
MethodSubject mMethodSubject = aClassSubject.uniqueMethodWithName("m");
assertThat(mMethodSubject, isPresent());
- // TODO(b/152196923): Should be 0.
assertEquals(
- 2,
+ 1,
countStaticGetInstructions(
mMethodSubject.asFoundMethodSubject(), fFieldSubject.asFoundFieldSubject()));
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
index 5ab6053..b5ad06f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -12,6 +12,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -50,11 +51,14 @@
import com.android.tools.r8.ir.optimize.staticizer.trivial.TrivialTestClass;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.InternalOptions;
+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.google.common.collect.Lists;
import com.google.common.collect.Streams;
+import java.io.IOException;
import java.util.List;
+import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,6 +68,37 @@
public class ClassStaticizerTest extends TestBase {
private final TestParameters parameters;
+ private static final String EXPECTED =
+ StringUtils.lines(
+ "Simple::bar(Simple::foo())",
+ "Simple::bar(0)",
+ "SimpleWithPhi$Companion::bar(SimpleWithPhi$Companion::foo()) true",
+ "SimpleWithSideEffects::<clinit>()",
+ "SimpleWithSideEffects::bar(SimpleWithSideEffects::foo())",
+ "SimpleWithSideEffects::bar(1)",
+ "SimpleWithParams::bar(SimpleWithParams::foo())",
+ "SimpleWithParams::bar(2)",
+ "SimpleWithGetter::bar(SimpleWithGetter::foo())",
+ "SimpleWithGetter::bar(3)",
+ "Simple::bar(Simple::foo())",
+ "Simple::bar(4)",
+ "Simple::bar(Simple::foo())",
+ "Simple::bar(5)");
+
+ private static final Class<?> main = TrivialTestClass.class;
+ private static final Class<?>[] classes = {
+ NeverInline.class,
+ TrivialTestClass.class,
+ Simple.class,
+ SimpleWithGetter.class,
+ SimpleWithLazyInit.class,
+ SimpleWithParams.class,
+ SimpleWithPhi.class,
+ SimpleWithPhi.Companion.class,
+ SimpleWithSideEffects.class,
+ SimpleWithThrowingGetter.class
+ };
+
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
// TODO(b/112831361): support for class staticizer in CF backend.
@@ -75,21 +110,20 @@
}
@Test
+ public void testWithoutAccessModification()
+ throws ExecutionException, CompilationFailedException, IOException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(classes)
+ .addKeepMainRule(main)
+ .addKeepAttributes("InnerClasses", "EnclosingMethod")
+ .addOptionsModification(this::configure)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), main)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
public void testTrivial() throws Exception {
- Class<?> main = TrivialTestClass.class;
- Class<?>[] classes = {
- NeverInline.class,
- TrivialTestClass.class,
- Simple.class,
- SimpleWithGetter.class,
- SimpleWithLazyInit.class,
- SimpleWithParams.class,
- SimpleWithPhi.class,
- SimpleWithPhi.Companion.class,
- SimpleWithSideEffects.class,
- SimpleWithThrowingGetter.class
- };
- String javaOutput = runOnJava(main);
TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
@@ -101,7 +135,7 @@
.allowAccessModification()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), main)
- .assertSuccessWithOutput(javaOutput);
+ .assertSuccessWithOutput(EXPECTED);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(main);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
index 55487ac..6bc91b4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
@@ -15,11 +15,11 @@
@NeverInline
String foo() {
- return bar("Simple::foo()");
+ return bar("SimpleWithGetter::foo()");
}
@NeverInline
String bar(String other) {
- return "Simple::bar(" + other + ")";
+ return "SimpleWithGetter::bar(" + other + ")";
}
}
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 1720b55..5799823 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
@@ -25,4 +25,5 @@
static final String KT_UNIT = "Lkotlin/Unit;";
static final String KT_FUNCTION1 = "Lkotlin/Function1;";
+ static final String KT_COMPARABLE = "Lkotlin/Comparable;";
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index 734ce50..b1af80f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -4,22 +4,29 @@
package com.android.tools.r8.kotlin.metadata;
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isDexClass;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
-import static org.hamcrest.CoreMatchers.containsString;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertNotEquals;
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.Kotlin.ClassClassifiers;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
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.KmClassifierSubject;
import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeAliasSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.nio.file.Path;
import java.util.Collection;
@@ -47,6 +54,7 @@
"true",
"42",
"1",
+ "ClassWithCompanion::fooOnCompanion",
"42",
"42",
"1",
@@ -107,33 +115,49 @@
.addProgramFiles(typeAliasLibJarMap.get(targetVersion))
// Keep non-private members of Impl
.addKeepRules("-keep class **.Impl { !private *; }")
- // Keep Itf, but allow minification.
- .addKeepRules("-keep,allowobfuscation class **.Itf")
+ // Keep but allow obfuscation of types.
+ .addKeepRules("-keep,allowobfuscation class " + PKG + ".typealias_lib.** { *; }")
+ .addKeepRules("-keepclassmembernames class " + PKG + ".typealias_lib.**" + " { *; }")
+ // Keep the Companion class for ClassWithCompanionC.
+ .addKeepRules("-keep class **.ClassWithCompanion$Companion { *; }")
+ // Keep the inner class, otherwise it cannot be constructed.
+ .addKeepRules("-keep class **.*Inner { *; }")
// Keep LibKt that contains the type-aliases and utils.
- .addKeepRules("-keep class **.LibKt { *; }")
- .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .addKeepRules("-keep class **.LibKt, **.Lib_extKt { *; }")
+ // Keep the library test methods
+ .addKeepRules("-keep class " + PKG + ".typealias_lib.*Tester { *; }")
+ .addKeepRules("-keep class " + PKG + ".typealias_lib.*Tester$Companion { *; }")
+ .addKeepAttributes(
+ ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+ ProguardKeepAttributes.SIGNATURE,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
.compile()
.inspect(this::inspect)
.writeToZip();
- ProcessResult kotlinTestCompileResult =
+ Path appJar =
kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
.addClasspathFiles(libJar)
.addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typealias_app", "main"))
- .setOutputPath(temp.newFolder().toPath())
- // TODO(b/151194785): update to just .compile() once fixed.
- .compileRaw();
- // TODO(b/151194785): should be able to compile!
- assertNotEquals(0, kotlinTestCompileResult.exitCode);
- assertThat(
- kotlinTestCompileResult.stderr,
- containsString(
- "type mismatch: inferred type is ProgramClass but API /* = Itf */ was expected"));
+ .compile();
+
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(appJar)
+ .run(parameters.getRuntime(), PKG + ".typealias_app.MainKt")
+ .assertSuccessWithOutput(EXPECTED);
}
private void inspect(CodeInspector inspector) {
- String itfClassName = PKG + ".typealias_lib.Itf";
- String libKtClassName = PKG + ".typealias_lib.LibKt";
+ inspectLib(inspector);
+ inspectLibExt(inspector);
+ }
+
+ private void inspectLib(CodeInspector inspector) {
+ String packageName = PKG + ".typealias_lib";
+ String itfClassName = packageName + ".Itf";
+ String libKtClassName = packageName + ".LibKt";
ClassSubject itf = inspector.clazz(itfClassName);
assertThat(itf, isRenamed());
@@ -149,6 +173,115 @@
// API entry is kept, hence the presence of Metadata.
KmPackageSubject kmPackage = libKt.getKmPackage();
assertThat(kmPackage, isPresent());
- // TODO(b/151194785): need further inspection: many kinds of type appearances in typealias.
+
+ String arrayDescriptor =
+ DescriptorUtils.getDescriptorFromKotlinClassifier(ClassClassifiers.arrayBinaryName);
+
+ // Check that typealias myAliasedArray<T> = Array<T> exists.
+ KmTypeAliasSubject myAliasedArray = kmPackage.kmTypeAliasWithUniqueName("myAliasedArray");
+ assertThat(myAliasedArray, isPresent());
+ assertEquals(arrayDescriptor, myAliasedArray.expandedType().descriptor());
+
+ // Check that typealias API = Itf has been rewritten correctly.
+ KmTypeAliasSubject api = kmPackage.kmTypeAliasWithUniqueName("API");
+ assertThat(api, isPresent());
+ assertThat(api.expandedType(), isDexClass(itf.getDexClass()));
+ assertThat(api.underlyingType(), isDexClass(itf.getDexClass()));
+
+ // Check that the type-alias APIs exist and that the expanded type is renamed.
+ KmTypeAliasSubject apIs = kmPackage.kmTypeAliasWithUniqueName("APIs");
+ assertThat(apIs, isPresent());
+ assertEquals(arrayDescriptor, apIs.expandedType().descriptor());
+ assertEquals(1, apIs.expandedType().typeArguments().size());
+ KmTypeProjectionSubject expandedArgument = apIs.expandedType().typeArguments().get(0);
+ assertThat(expandedArgument.type(), isDexClass(itf.getDexClass()));
+
+ assertEquals(myAliasedArray.descriptor(packageName), apIs.underlyingType().descriptor());
+ assertEquals(1, apIs.underlyingType().typeArguments().size());
+ KmTypeProjectionSubject underlyingArgument = apIs.underlyingType().typeArguments().get(0);
+ KmTypeSubject type = underlyingArgument.type();
+ assertNotNull(type);
+ assertTrue(type.classifier().isTypeAlias());
+ assertEquals(api.descriptor(packageName), type.descriptor());
+ }
+
+ private void inspectLibExt(CodeInspector inspector) {
+ String packageName = PKG + ".typealias_lib";
+ String libKtClassName = packageName + ".Lib_extKt";
+
+ // Check that Arr has been renamed.
+ ClassSubject arr = inspector.clazz(packageName + ".Arr");
+ assertThat(arr, isRenamed());
+
+ ClassSubject libKt = inspector.clazz(libKtClassName);
+ KmPackageSubject kmPackage = libKt.getKmPackage();
+
+ // typealias Arr1D<K> = Arr<K>
+ KmTypeAliasSubject arr1D = kmPackage.kmTypeAliasWithUniqueName("Arr1D");
+ assertThat(arr1D, isPresent());
+ assertThat(arr1D.expandedType(), isDexClass(arr.getDexClass()));
+
+ // typealias Arr2D<K> = Arr1D<Arr1D<K>>
+ KmTypeAliasSubject arr2D = kmPackage.kmTypeAliasWithUniqueName("Arr2D");
+ assertThat(arr2D, isPresent());
+ assertThat(arr2D.expandedType(), isDexClass(arr.getDexClass()));
+ assertEquals(1, arr2D.expandedType().typeArguments().size());
+ KmTypeProjectionSubject arr2DexpandedArg = arr2D.expandedType().typeArguments().get(0);
+ assertThat(arr2DexpandedArg.type(), isDexClass(arr.getDexClass()));
+
+ assertEquals(arr1D.descriptor(packageName), arr2D.underlyingType().descriptor());
+ assertEquals(1, arr2D.underlyingType().typeArguments().size());
+ KmTypeProjectionSubject arr2DunderlyingArg = arr2D.underlyingType().typeArguments().get(0);
+ assertEquals(arr1D.descriptor(packageName), arr2DunderlyingArg.type().descriptor());
+
+ // typealias IntSet = Set<Int>
+ // typealias MyMapToSetOfInt<K> = MutableMap<K, IntSet>
+ KmTypeAliasSubject intSet = kmPackage.kmTypeAliasWithUniqueName("IntSet");
+ assertThat(intSet, isPresent());
+
+ KmTypeAliasSubject myMapToSetOfInt = kmPackage.kmTypeAliasWithUniqueName("MyMapToSetOfInt");
+ assertThat(myMapToSetOfInt, isPresent());
+ assertEquals(2, myMapToSetOfInt.underlyingType().typeArguments().size());
+ assertEquals(2, myMapToSetOfInt.expandedType().typeArguments().size());
+ assertEquals(1, myMapToSetOfInt.typeParameters().size());
+ KmClassifierSubject typeClassifier =
+ myMapToSetOfInt.underlyingType().typeArguments().get(0).type().classifier();
+ assertTrue(typeClassifier.isTypeParameter());
+ // Check that the type-variable K in 'MyMapToSetOfInt<K>' is the first argument in
+ // MutableMap<K, IntSet>.
+ assertEquals(
+ myMapToSetOfInt.typeParameters().get(0).getId(), typeClassifier.asTypeParameter().getId());
+
+ KmTypeSubject underlyingType = myMapToSetOfInt.underlyingType().typeArguments().get(1).type();
+ assertEquals(intSet.descriptor(packageName), underlyingType.descriptor());
+
+ KmTypeSubject expandedType = myMapToSetOfInt.expandedType().typeArguments().get(1).type();
+ assertEquals(intSet.expandedType(), expandedType);
+
+ // Check that the following exist:
+ // typealias MyHandler = (Int, Any) -> Unit
+ // typealias MyGenericPredicate<T> = (T) -> Boolean
+ assertThat(kmPackage.kmTypeAliasWithUniqueName("MyHandler"), isPresent());
+ KmTypeAliasSubject genericPredicate = kmPackage.kmTypeAliasWithUniqueName("MyGenericPredicate");
+ assertThat(genericPredicate, isPresent());
+
+ // Check that the type-variable T in 'MyGenericPredicate<T>' is the input argument in
+ // (T) -> Boolean.
+ assertEquals(1, genericPredicate.typeParameters().size());
+ assertEquals(2, genericPredicate.expandedType().typeArguments().size());
+ KmTypeProjectionSubject kmTypeGenericArgumentSubject =
+ genericPredicate.expandedType().typeArguments().get(0);
+ assertTrue(kmTypeGenericArgumentSubject.type().classifier().isTypeParameter());
+ assertEquals(
+ genericPredicate.typeParameters().get(0).getId(),
+ kmTypeGenericArgumentSubject.type().classifier().asTypeParameter().getId());
+
+ // typealias ClassWithCompanionC = ClassWithCompanion.Companion
+ KmTypeAliasSubject classWithCompanionC =
+ kmPackage.kmTypeAliasWithUniqueName("ClassWithCompanionC");
+ assertThat(classWithCompanionC, isPresent());
+
+ ClassSubject companionClazz = inspector.clazz(packageName + ".ClassWithCompanion$Companion");
+ assertThat(classWithCompanionC.expandedType(), isDexClass(companionClazz.getDexClass()));
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
index 3d3d935..a158b99 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -4,17 +4,38 @@
package com.android.tools.r8.kotlin.metadata;
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isDexClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isExtensionFunction;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
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.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPackageSubject;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeParameterSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeParameterSubjectMixin;
+import com.android.tools.r8.utils.codeinspector.KmTypeProjectionSubject;
+import com.android.tools.r8.utils.codeinspector.KmTypeSubject;
+import com.android.tools.r8.utils.codeinspector.KmValueParameterSubject;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import kotlinx.metadata.KmClassifier.TypeParameter;
+import kotlinx.metadata.KmVariance;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -22,6 +43,12 @@
@RunWith(Parameterized.class)
public class MetadataRewriteInTypeArgumentsTest extends KotlinMetadataTestBase {
+
+ private static final String LIB_PKG = PKG + ".typeargument_lib.";
+
+ private static int FLAG_NONE = 0;
+ private static int FLAG_REIFIED = 1;
+
private static final String EXPECTED =
StringUtils.lines(
"Hello World!",
@@ -43,8 +70,13 @@
"42",
"1",
"2",
+ "9",
+ "3",
"7",
- "42");
+ "9",
+ "42",
+ "42",
+ "7");
private final TestParameters parameters;
@@ -69,7 +101,6 @@
Path typeAliasLibJar =
kotlinc(KOTLINC, targetVersion)
.addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib"))
- .addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib_minified"))
.compile();
jarMap.put(targetVersion, typeAliasLibJar);
}
@@ -94,20 +125,27 @@
}
@Test
- public void testMetadataInTypeAlias_keepAll() throws Exception {
+ public void testMetadataInTypeAliasWithR8() throws Exception {
Path libJar =
testForR8(parameters.getBackend())
.addProgramFiles(jarMap.get(targetVersion))
- .addKeepAllClassesRule()
+ // Keep ClassThatWillBeObfuscated, but allow minification.
+ .addKeepRules("-keep,allowobfuscation class **ClassThatWillBeObfuscated")
+ .addKeepRules("-keepclassmembers class **ClassThatWillBeObfuscated { *; }")
+ // Keep all other classes.
+ .addKeepRules("-keep class **typeargument_lib.PlainBox { *; }")
+ .addKeepRules("-keep class **typeargument_lib.SomeClass { *; }")
+ .addKeepRules("-keep class **typeargument_lib.CoVariant { *; }")
+ .addKeepRules("-keep class **typeargument_lib.ContraVariant { *; }")
+ .addKeepRules("-keep class **typeargument_lib.Invariant { *; }")
+ .addKeepRules("-keep class **typeargument_lib.LibKt { *; }")
.addKeepAttributes(
ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
ProguardKeepAttributes.SIGNATURE,
ProguardKeepAttributes.INNER_CLASSES,
ProguardKeepAttributes.ENCLOSING_METHOD)
.compile()
- // TODO(b/151925520): Add inspections when program compiles correctly.
- // TODO(mkroghj): Also inspect the renaming of lib_minified
- // (not now, but when program compiles correctly).
+ .inspect(this::inspect)
.writeToZip();
Path mainJar =
@@ -118,13 +156,124 @@
// TODO(b/152306391): Reified type-parameters are not flagged correctly.
testForJvm()
- .addProgramFiles(mainJar)
- .addProgramFiles(ToolHelper.getKotlinStdlibJar())
- .addRunClasspathFiles(libJar)
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(mainJar)
.run(parameters.getRuntime(), PKG + ".typeargument_app.MainKt")
.assertFailureWithErrorThatMatches(
containsString(
"This function has a reified type parameter and thus can only be inlined at"
+ " compilation time, not called directly"));
}
+
+ private void inspect(CodeInspector inspector) {
+ inspectInvariant(inspector);
+ inspectCoVariant(inspector);
+ inspectContraVariant(inspector);
+ inspectExtensions(inspector);
+ }
+
+ private void inspectInvariant(CodeInspector inspector) {
+ ClassSubject someClass = inspector.clazz(LIB_PKG + "SomeClass");
+ assertThat(someClass, isPresent());
+ ClassSubject classThatShouldBeObfuscated =
+ inspector.clazz(LIB_PKG + "ClassThatWillBeObfuscated");
+ assertThat(classThatShouldBeObfuscated, isRenamed());
+
+ // Check that the type-parameters of Invariant is marked as INVARIANT.
+ ClassSubject invariant = inspector.clazz(LIB_PKG + "Invariant");
+ assertThat(invariant, isPresent());
+ KmClassSubject kmClass = invariant.getKmClass();
+ assertThat(kmClass, isPresent());
+ assertEquals(2, kmClass.typeParameters().size());
+ inspectTypeParameter(kmClass, "T", 0, FLAG_NONE, KmVariance.INVARIANT);
+ inspectTypeParameter(kmClass, "C", 1, FLAG_NONE, KmVariance.INVARIANT);
+
+ // Check that funGenerics method has a type-parameter with id = 2.
+ KmFunctionSubject funGenerics = kmClass.kmFunctionWithUniqueName("funGenerics");
+ assertThat(funGenerics, isPresent());
+ assertEquals(1, funGenerics.typeParameters().size());
+ inspectTypeParameter(funGenerics, "R", 2, FLAG_NONE, KmVariance.INVARIANT);
+ assertEquals(1, funGenerics.valueParameters().size());
+ KmValueParameterSubject kmValueParameterSubject = funGenerics.valueParameters().get(0);
+ assertTrue(kmValueParameterSubject.type().classifier().isTypeParameter());
+ TypeParameter typeParameter = kmValueParameterSubject.type().classifier().asTypeParameter();
+ assertEquals(2, typeParameter.getId());
+
+ // Check that the funGenerics method return type is referencing the method type parameter.
+ KmTypeSubject funGenericsReturnType = funGenerics.returnType();
+ assertTrue(funGenericsReturnType.classifier().isTypeParameter());
+ assertEquals(2, funGenericsReturnType.classifier().asTypeParameter().getId());
+
+ // Check funGenericsWithUpperBounds has an upperBound of SomeClass.
+ KmFunctionSubject funGenericsWithUpperBounds =
+ kmClass.kmFunctionWithUniqueName("funGenericsWithUpperBounds");
+ assertThat(funGenericsWithUpperBounds, isPresent());
+ assertEquals(1, funGenericsWithUpperBounds.typeParameters().size());
+ inspectTypeParameter(funGenericsWithUpperBounds, "R", 2, FLAG_NONE, KmVariance.INVARIANT);
+ KmTypeParameterSubject methodTypeParameter = funGenericsWithUpperBounds.typeParameters().get(0);
+ List<KmTypeSubject> upperBounds = methodTypeParameter.upperBounds();
+ assertEquals(2, upperBounds.size());
+ assertThat(upperBounds.get(0), isDexClass(someClass.getDexClass()));
+ assertEquals(KT_COMPARABLE, upperBounds.get(1).descriptor());
+ // Check that the upper bound has a type argument.
+ assertEquals(1, upperBounds.get(1).typeArguments().size());
+ assertThat(
+ upperBounds.get(1).typeArguments().get(0).type(), isDexClass(someClass.getDexClass()));
+ }
+
+ private void inspectCoVariant(CodeInspector inspector) {
+ // Check that the type-parameter for CoVariant is marked as OUT.
+ ClassSubject invariant = inspector.clazz(LIB_PKG + "CoVariant");
+ assertThat(invariant, isPresent());
+ KmClassSubject kmClass = invariant.getKmClass();
+ assertThat(kmClass, isPresent());
+ assertEquals(1, kmClass.typeParameters().size());
+ inspectTypeParameter(kmClass, "T", 0, FLAG_NONE, KmVariance.OUT);
+ // Check that the return type of the property CoVariant.t refers to the type parameter.
+ assertEquals(1, kmClass.getProperties().size());
+ KmPropertySubject t = kmClass.kmPropertyWithUniqueName("t");
+ assertThat(t, isPresent());
+ assertTrue(t.returnType().typeArguments().isEmpty());
+ assertEquals(
+ kmClass.typeParameters().get(0).getId(),
+ t.returnType().classifier().asTypeParameter().getId());
+ }
+
+ private void inspectContraVariant(CodeInspector inspector) {
+ // Check that the type-parameter for ContraVariant is marked as IN.
+ ClassSubject invariant = inspector.clazz(LIB_PKG + "ContraVariant");
+ assertThat(invariant, isPresent());
+ KmClassSubject kmClass = invariant.getKmClass();
+ assertThat(kmClass, isPresent());
+ assertEquals(1, kmClass.typeParameters().size());
+ inspectTypeParameter(kmClass, "T", 0, FLAG_NONE, KmVariance.IN);
+ }
+
+ private void inspectExtensions(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(LIB_PKG + "LibKt");
+ assertThat(clazz, isPresent());
+ KmPackageSubject kmPackage = clazz.getKmPackage();
+ assertThat(kmPackage, isPresent());
+ KmFunctionSubject asListWithVarargs =
+ kmPackage.kmFunctionExtensionWithUniqueName("asListWithVarargs");
+ assertThat(asListWithVarargs, isExtensionFunction());
+ inspectTypeParameter(asListWithVarargs, "T", 0, FLAG_REIFIED, KmVariance.INVARIANT);
+ // Check that the varargs type argument has OUT invariance.
+ List<KmTypeProjectionSubject> kmTypeProjectionSubjects =
+ asListWithVarargs.returnType().typeArguments();
+ assertEquals(1, kmTypeProjectionSubjects.size());
+ KmTypeSubject type = kmTypeProjectionSubjects.get(0).type();
+ assertEquals(1, type.typeArguments().size());
+ KmTypeProjectionSubject kmTypeProjectionSubject = type.typeArguments().get(0);
+ assertEquals(KmVariance.OUT, kmTypeProjectionSubject.variance());
+ }
+
+ private void inspectTypeParameter(
+ KmTypeParameterSubjectMixin subject, String name, int id, int flags, KmVariance variance) {
+ KmTypeParameterSubject typeParameter = subject.kmTypeParameterWithUniqueName(name);
+ assertThat(typeParameter, isPresent());
+ assertEquals(id, typeParameter.getId());
+ assertEquals(flags, typeParameter.getFlags());
+ assertEquals(variance, typeParameter.getVariance());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt
index 396c2e4..f836216 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt
@@ -5,7 +5,7 @@
import com.android.tools.r8.kotlin.metadata.typealias_lib.API
import com.android.tools.r8.kotlin.metadata.typealias_lib.AlphaNaming
-import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr
+import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr1D
import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr2D
import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr2DTester
import com.android.tools.r8.kotlin.metadata.typealias_lib.CWithConstructor
@@ -13,6 +13,7 @@
import com.android.tools.r8.kotlin.metadata.typealias_lib.ClassWithCompanionC
import com.android.tools.r8.kotlin.metadata.typealias_lib.FunctionTester
import com.android.tools.r8.kotlin.metadata.typealias_lib.Impl
+import com.android.tools.r8.kotlin.metadata.typealias_lib.IntSet
import com.android.tools.r8.kotlin.metadata.typealias_lib.InterfaceTester
import com.android.tools.r8.kotlin.metadata.typealias_lib.MyAdvancedMap
import com.android.tools.r8.kotlin.metadata.typealias_lib.MyI
@@ -22,11 +23,10 @@
import com.android.tools.r8.kotlin.metadata.typealias_lib.OuterTester
import com.android.tools.r8.kotlin.metadata.typealias_lib.SimpleClassTester
import com.android.tools.r8.kotlin.metadata.typealias_lib.StillCWithConstructor
-import com.android.tools.r8.kotlin.metadata.typealias_lib.UnderlyingTypeTest
+import com.android.tools.r8.kotlin.metadata.typealias_lib.UnderlyingTypeTester
import com.android.tools.r8.kotlin.metadata.typealias_lib.UnusedTypeArgument
import com.android.tools.r8.kotlin.metadata.typealias_lib.VerticalClassMergingTester
import com.android.tools.r8.kotlin.metadata.typealias_lib.seq
-import com.android.tools.r8.kotlin.metadata.typealias_lib.IntSet as IntSet1
class ProgramClass : Impl() {
override fun foo(): API {
@@ -46,7 +46,7 @@
}
fun testArr2D() {
- val arr1d : Arr<Int> = Arr(42);
+ val arr1d : Arr1D<Int> = Arr1D(42);
val arr2d : Arr2D<Int> = Arr2D(arr1d);
println(Arr2DTester.f(Arr2DTester.g(arr2d)).x.x);
}
@@ -57,10 +57,10 @@
println("42");
}
}
- InterfaceTester.f(InterfaceTester.g(myInstance)).f()
+ InterfaceTester.f(myInstance).f()
val map : MyMapToSetOfInt<Int> = HashMap();
- val set : IntSet1 = mutableSetOf(42);
+ val set : IntSet = mutableSetOf(42);
map.put(1, set);
println(InterfaceTester.i(InterfaceTester.h(map))[1]?.iterator()?.next() ?: "");
}
@@ -79,12 +79,12 @@
fun testNestedClasses() {
val nested = OuterNested(42);
val myInner : OuterNestedInner = nested.Inner(1)
- println(OuterTester.g(nested).y)
- println(OuterTester.f(myInner).x)
+ println(OuterTester.f(OuterTester.g(nested)).y)
+ println(OuterTester.h(OuterTester.i(myInner)).x)
}
fun testCompanion() {
- ClassWithCompanionC.foo;
+ println(ClassWithCompanionC.fooOnCompanion);
}
fun testConstructor() {
@@ -93,12 +93,12 @@
fun testUnderlyingType() {
val cWithConstructor = StillCWithConstructor(42)
- println(UnderlyingTypeTest.f(UnderlyingTypeTest.g(cWithConstructor)).x)
+ println(UnderlyingTypeTester.f(UnderlyingTypeTester.g(cWithConstructor)).x)
val advancedMap : MyAdvancedMap = HashMap();
val nested = OuterNested(42);
val myInner : OuterNestedInner = nested.Inner(1)
advancedMap.put(nested, myInner);
- val sameMap = UnderlyingTypeTest.h(UnderlyingTypeTest.i(advancedMap))
+ val sameMap = UnderlyingTypeTester.h(UnderlyingTypeTester.i(advancedMap))
println(sameMap.get(nested)?.x)
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt
index ba63667..60d5bda 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt
@@ -1,6 +1,8 @@
// 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.
+@file:Suppress("UNCHECKED_CAST")
+
package com.android.tools.r8.kotlin.metadata.typealias_lib
// Unused type aliases
@@ -51,13 +53,14 @@
class SimpleClassTester {
companion object {
- fun f(a : SimpleClass) : AlphaNaming {
+ fun f(a : Any) : AlphaNaming {
+ return a as AlphaNaming;
+ }
+
+ fun g(a : AlphaNaming) : Any {
return a;
}
- fun g(a : AlphaNaming) : SimpleClass {
- return a;
- }
}
}
@@ -196,11 +199,11 @@
companion object {
- fun f(i : I<Int>) : MyI {
- return i;
+ fun f(i : Any) : MyI {
+ return i as MyI
}
- fun g(myI : MyI) : I<Int> {
+ fun g(myI : MyI) : Any {
return myI;
}
@@ -301,14 +304,21 @@
companion object {
- fun f(a : Outer.Nested.Inner) : OuterNestedInner {
+ fun f(a : Any) : OuterNested {
+ return a as OuterNested;
+ }
+
+ fun g(a : OuterNested) : Any {
return a;
}
- fun g(a : OuterNested) : Outer.Nested {
- return a;
+ fun h(a : Any) : OuterNestedInner {
+ return a as OuterNestedInner;
}
+ fun i(a : OuterNestedInner) : Any {
+ return a;
+ }
}
}
@@ -317,8 +327,8 @@
class ClassWithCompanion {
companion object {
- val foo: String
- get() = "A.Companion::foo"
+ val fooOnCompanion: String
+ get() = "ClassWithCompanion::fooOnCompanion"
}
}
@@ -355,11 +365,11 @@
companion object {
- fun f(a : C) : CWithConstructor {
- return a;
+ fun f(a : Any) : CWithConstructor {
+ return a as CWithConstructor;
}
- fun g(a : CWithConstructor) : C {
+ fun g(a : CWithConstructor) : Any {
return a;
}
}
@@ -391,7 +401,7 @@
// },
typealias MyAdvancedMap = MutableMap<OuterNested, OuterNestedInner>
-class UnderlyingTypeTest {
+class UnderlyingTypeTester {
companion object {
@@ -399,7 +409,7 @@
return a;
}
- fun g(a : C) : StillCWithConstructor {
+ fun g(a : CWithConstructor) : StillCWithConstructor {
return a;
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
index c8f4a72..2129081 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
@@ -7,9 +7,13 @@
import com.android.tools.r8.kotlin.metadata.typeargument_lib.CoVariant
import com.android.tools.r8.kotlin.metadata.typeargument_lib.ContraVariant
import com.android.tools.r8.kotlin.metadata.typeargument_lib.Invariant
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.PlainBox
import com.android.tools.r8.kotlin.metadata.typeargument_lib.SomeClass
import com.android.tools.r8.kotlin.metadata.typeargument_lib.asList
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.asListWithVarargs
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.asListWithVarargs2
import com.android.tools.r8.kotlin.metadata.typeargument_lib.asObfuscatedClass
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.asStar
import com.android.tools.r8.kotlin.metadata.typeargument_lib.unBoxAndBox
import com.android.tools.r8.kotlin.metadata.typeargument_lib.unboxAndPutInBox
import com.android.tools.r8.kotlin.metadata.typeargument_lib.update
@@ -48,10 +52,20 @@
println(CoVariant(1).update(42).t)
println(CoVariant(1).unboxAndPutInBox(CoVariant(42)).t)
println(CoVariant(42).asList().t.get(0))
- val asList = CoVariant(42).asList(1, 2)
+ val asList = CoVariant(42).asListWithVarargs(1, 2)
println(asList.t.get(0))
println(asList.t.get(1))
- println(CoVariant(7).asObfuscatedClass().t.get(0).get(0).x)
+ println(CoVariant(9).asListWithVarargs2(CoVariant(3)).t.get(0))
+ // Peeking into the result will result in an error since the underlying type has been renamed.
+ CoVariant(7).asObfuscatedClass()
+ println(CoVariant(42).asStar().t)
+}
+
+fun testPlainBox() {
+ var plainBox = PlainBox(42)
+ println(plainBox.plainBox)
+ plainBox.plainBox = 7
+ println(plainBox.plainBox)
}
fun main() {
@@ -59,4 +73,5 @@
testCoVariant()
testContraVariant();
testExtension()
+ testPlainBox()
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
index 38234b4..d9d3bea 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
@@ -6,6 +6,10 @@
open class SomeClass
+class PlainBox<T>(var plainBox : T)
+
+class ClassThatWillBeObfuscated(val x : Int)
+
class Invariant<T, C> {
constructor(someValue : C) {
@@ -76,12 +80,23 @@
return CoVariant(arrayOf(this.t))
}
-inline fun <reified T> CoVariant<T>.asList(vararg ts : T) : CoVariant<Array<out T>> {
+inline fun <reified T> CoVariant<T>.asListWithVarargs(vararg ts : T) : CoVariant<Array<out T>> {
println(this.t)
return CoVariant(ts)
}
+fun <T> CoVariant<T>.asListWithVarargs2(vararg ts : CoVariant<T>) : CoVariant<List<T>> {
+ println(this.t)
+ return CoVariant(listOf(ts.get(0).t))
+}
+
fun <T> CoVariant<T>.asObfuscatedClass() : CoVariant<Array<Array<ClassThatWillBeObfuscated>>> {
println(this.t)
- return CoVariant(arrayOf(arrayOf(ClassThatWillBeObfuscated(42))))
+ val classThatWillBeObfuscated = ClassThatWillBeObfuscated(9)
+ println(classThatWillBeObfuscated.x)
+ return CoVariant(arrayOf(arrayOf(classThatWillBeObfuscated)))
+}
+
+fun CoVariant<*>.asStar() : CoVariant<*> {
+ return this;
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt
deleted file mode 100644
index 78c3742..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib_minified.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.android.tools.r8.kotlin.metadata.typeargument_lib
-
-class ClassThatWillBeObfuscated(val x : Int)
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
new file mode 100644
index 0000000..63fe00f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingDesugarLambdaTest.java
@@ -0,0 +1,120 @@
+package com.android.tools.r8.naming.applymapping;
+
+import static com.android.tools.r8.Collectors.toSingle;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+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.FileUtils;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/152715309.
+@RunWith(Parameterized.class)
+public class ApplyMappingDesugarLambdaTest extends TestBase {
+
+ private static final String EXPECTED = "FOO";
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ }
+
+ public ApplyMappingDesugarLambdaTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws CompilationFailedException, IOException, ExecutionException {
+ // Create a dictionary to control the naming.
+ Path dictionary = temp.getRoot().toPath().resolve("dictionary.txt");
+ FileUtils.writeTextFile(dictionary, "e");
+
+ final String finalName = "com.android.tools.r8.naming.applymapping.e";
+
+ R8TestCompileResult libraryResult =
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(A.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules(
+ "-keeppackagenames", "-classobfuscationdictionary " + dictionary.toString())
+ .compile()
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(A.class), isRenamed());
+ assertEquals(finalName, inspector.clazz(A.class).getFinalName());
+ });
+
+ Path libraryPath = libraryResult.writeToZip();
+
+ // Ensure that the library works as supposed.
+ testForD8()
+ .addProgramClasses(I.class, Main.class)
+ .addClasspathFiles(libraryPath)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class, EXPECTED)
+ .assertSuccessWithOutputLines(EXPECTED);
+
+ testForR8(parameters.getBackend())
+ .addClasspathClasses(A.class)
+ .addProgramClasses(I.class, Main.class)
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(I.class)
+ .setMinApi(parameters.getApiLevel())
+ .addApplyMapping(libraryResult.getProguardMap())
+ .addOptionsModification(internalOptions -> internalOptions.enableClassInlining = false)
+ .addKeepRules("-classobfuscationdictionary " + dictionary.toString())
+ .compile()
+ .inspect(
+ inspector -> {
+ // Assert that there is a lambda class created.
+ assertEquals(3, inspector.allClasses().size());
+ FoundClassSubject lambdaClass =
+ inspector.allClasses().stream()
+ .filter(FoundClassSubject::isSynthetic)
+ .collect(toSingle());
+ assertNotSame(finalName, lambdaClass.getFinalName());
+ })
+ .addRunClasspathFiles(libraryPath)
+ .run(parameters.getRuntime(), Main.class, EXPECTED)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class A {
+
+ A(int bar) {
+ System.out.println(bar);
+ }
+ }
+
+ @FunctionalInterface
+ public interface I {
+
+ void doStuff();
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ processI(() -> System.out.println(args[0]));
+ }
+
+ public static void processI(I i) {
+ i.doStuff();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b151964517/ConstStringWithMonitorTest.java b/src/test/java/com/android/tools/r8/regress/b151964517/ConstStringWithMonitorTest.java
index 7c8aa34..c37953b 100644
--- a/src/test/java/com/android/tools/r8/regress/b151964517/ConstStringWithMonitorTest.java
+++ b/src/test/java/com/android/tools/r8/regress/b151964517/ConstStringWithMonitorTest.java
@@ -16,7 +16,7 @@
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().build();
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
}
public ConstStringWithMonitorTest(TestParameters parameters) {
@@ -28,11 +28,12 @@
testForR8(parameters.getBackend())
.addInnerClasses(ConstStringWithMonitorTest.class)
.noMinification()
+ .setMinApi(parameters.getApiLevel())
.allowAccessModification()
.addKeepMainRule(TestClass.class)
.compile()
- .runDex2Oat(parameters.getRuntime());
- // TODO(b/151964517): Should pass verification with assertNoVerificationErrors()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("foobar");
}
public static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/regress/b152800551/FailedStaticizingRegressionTest.java b/src/test/java/com/android/tools/r8/regress/b152800551/FailedStaticizingRegressionTest.java
new file mode 100644
index 0000000..7ec8ab0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b152800551/FailedStaticizingRegressionTest.java
@@ -0,0 +1,81 @@
+// 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.regress.b152800551;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// This is a reproduction of b/152800551.
+@RunWith(Parameterized.class)
+public class FailedStaticizingRegressionTest extends TestBase {
+
+ private static final String EXPECTED =
+ StringUtils.lines(
+ "S::foo a 1", "S::foo a 2", "S::foo a 3", "S::foo b 1", "S::foo b 2", "S::foo b 3");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public FailedStaticizingRegressionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ static class S {
+
+ private static S f = new S();
+
+ public static S get() {
+ return f;
+ }
+
+ public void foo() {
+ // Double call of foo so single call inlining does not trigger.
+ foo("a");
+ foo("b");
+ }
+
+ @NeverInline
+ public void foo(String s) {
+ System.out.println("S::foo " + s + " 1");
+ System.out.println("S::foo " + s + " 2");
+ System.out.println("S::foo " + s + " 3");
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ S.get().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.java
new file mode 100644
index 0000000..36972c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b152973695/CompileToInvalidFileTest.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.regress.b152973695;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CompileToInvalidFileTest extends TestBase {
+ private static final Path INVALID_FILE = Paths.get("!@#/\\INVALID_FILE");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public CompileToInvalidFileTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testCompileToInvalidFileD8() {
+ ensureInvalidFileIsInvalid();
+ try {
+ testForD8()
+ .addProgramClasses(Main.class)
+ .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(INVALID_FILE))
+ .compile();
+ fail("Expected a CompilationFailedException but the code succeeded");
+ } catch (CompilationFailedException ex) {
+ assertInvalidFileNotFound(ex);
+ } catch (Throwable t) {
+ fail("Expected a CompilationFailedException but got instead " + t);
+ }
+ }
+
+ @Test
+ public void testCompileToInvalidFileR8() {
+ ensureInvalidFileIsInvalid();
+ try {
+ testForR8(Backend.CF)
+ .addProgramClasses(Main.class)
+ .addKeepMainRule(Main.class)
+ .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(INVALID_FILE))
+ .compile();
+ fail("Expected a CompilationFailedException but the code succeeded");
+ } catch (CompilationFailedException ex) {
+ assertInvalidFileNotFound(ex);
+ } catch (Throwable t) {
+ fail("Expected a CompilationFailedException but got instead " + t);
+ }
+ }
+
+ private void assertInvalidFileNotFound(CompilationFailedException ex) {
+ assertTrue(ex.getCause().getMessage().contains("File not found"));
+ assertTrue(ex.getCause().getMessage().contains(INVALID_FILE.toString()));
+ }
+
+ private void ensureInvalidFileIsInvalid() {
+ try {
+ Files.newOutputStream(
+ INVALID_FILE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+ fail("Expected an IOException but the code succeeded");
+ } catch (IOException ignored) {
+ } catch (Throwable t) {
+ fail("Expected an IOException but got instead " + t);
+ }
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java
new file mode 100644
index 0000000..6f061f3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/enums/EnumValueOfOptimizationTest.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.enums;
+
+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 EnumValueOfOptimizationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EnumValueOfOptimizationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testValueOf() throws Exception {
+ testForR8(parameters.getBackend())
+ .addKeepMainRule(Main.class)
+ .addInnerClasses(EnumValueOfOptimizationTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("npe OK", "iae1 OK", "iae2 OK", "iae3 OK", "iae4 OK");
+ }
+
+ enum MyEnum {
+ A,
+ B
+ }
+
+ enum ComplexEnum {
+ A {
+ @Override
+ public String toString() {
+ return "a0";
+ }
+ },
+ B
+ }
+
+ @SuppressWarnings({"unchecked", "ConstantConditions"})
+ static class Main {
+ public static void main(String[] args) {
+ myEnumTest();
+ complexEnumTest();
+ }
+
+ private static void complexEnumTest() {
+ Enum<?> e = null;
+ try {
+ e = subtypeError();
+ System.out.println("iae4 KO");
+ } catch (IllegalArgumentException iae) {
+ System.out.println("iae4 OK");
+ }
+ if (e != null) {
+ throw new Error("enum set");
+ }
+ }
+
+ private static Enum<?> subtypeError() {
+ return Enum.valueOf((Class) ComplexEnum.A.getClass(), "A");
+ }
+
+ private static void myEnumTest() {
+ Enum<?> e = null;
+ try {
+ e = nullClassError();
+ System.out.println("npe KO");
+ } catch (NullPointerException ignored) {
+ System.out.println("npe OK");
+ }
+ if (e != null) {
+ throw new Error("enum set");
+ }
+ try {
+ e = invalidNameError();
+ System.out.println("iae1 KO");
+ } catch (IllegalArgumentException iae) {
+ System.out.println("iae1 OK");
+ }
+ if (e != null) {
+ throw new Error("enum set");
+ }
+ try {
+ e = enumClassError();
+ System.out.println("iae2 KO");
+ } catch (IllegalArgumentException iae) {
+ System.out.println("iae2 OK");
+ }
+ if (e != null) {
+ throw new Error("enum set");
+ }
+ try {
+ e = voidError();
+ System.out.println("iae3 KO");
+ } catch (IllegalArgumentException iae) {
+ System.out.println("iae3 OK");
+ }
+ if (e != null) {
+ throw new Error("enum set");
+ }
+ }
+
+ private static Enum<?> voidError() {
+ return Enum.valueOf((Class) Void.class, "TYPE");
+ }
+
+ private static Enum<?> enumClassError() {
+ return Enum.valueOf(Enum.class, "smth");
+ }
+
+ private static Enum<?> invalidNameError() {
+ return Enum.valueOf(MyEnum.class, "curly");
+ }
+
+ private static Enum<?> nullClassError() {
+ return Enum.valueOf((Class<MyEnum>) null, "a string");
+ }
+ }
+}
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 530b5b5..7bc8279 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.references.ClassReference;
@@ -228,6 +229,25 @@
});
}
+ public ClassFileTransformer setAccessFlags(Consumer<ClassAccessFlags> fn) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ ClassAccessFlags flags = ClassAccessFlags.fromCfAccessFlags(access);
+ fn.accept(flags);
+ super.visit(
+ version, flags.getAsCfAccessFlags(), name, signature, superName, interfaces);
+ }
+ });
+ }
+
public ClassFileTransformer setNest(Class<?> host, Class<?>... members) {
assert !Arrays.asList(members).contains(host);
return setMinVersion(CfVm.JDK11)
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index a009244..a58d5ee 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -3,8 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexClass;
import java.util.List;
+import kotlinx.metadata.KmTypeParameter;
public class AbsentKmClassSubject extends KmClassSubject {
@@ -25,12 +27,12 @@
@Override
public boolean isRenamed() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmClass is renamed");
}
@Override
public boolean isSynthetic() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmClass is synthetic");
}
@Override
@@ -94,6 +96,16 @@
}
@Override
+ public List<KmTypeAliasSubject> getTypeAliases() {
+ return null;
+ }
+
+ @Override
+ public KmTypeAliasSubject kmTypeAliasWithUniqueName(String name) {
+ return null;
+ }
+
+ @Override
public List<String> getSuperTypeDescriptors() {
return null;
}
@@ -127,4 +139,14 @@
public String getCompanionObject() {
return null;
}
+
+ @Override
+ public List<KmTypeParameter> getKmTypeParameters() {
+ return null;
+ }
+
+ @Override
+ public CodeInspector getCodeInspector() {
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
index 5efcf6e..7e8c837 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmFunctionSubject.java
@@ -3,7 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.errors.Unreachable;
import java.util.List;
+import kotlinx.metadata.KmTypeParameter;
import kotlinx.metadata.jvm.JvmMethodSignature;
public class AbsentKmFunctionSubject extends KmFunctionSubject {
@@ -15,17 +17,17 @@
@Override
public boolean isRenamed() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmFunction is renamed");
}
@Override
public boolean isSynthetic() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmFunction is synthetic");
}
@Override
public boolean isExtension() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmFunction is extension");
}
@Override
@@ -47,4 +49,14 @@
public KmTypeSubject returnType() {
return null;
}
+
+ @Override
+ public List<KmTypeParameter> getKmTypeParameters() {
+ return null;
+ }
+
+ @Override
+ public CodeInspector getCodeInspector() {
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
index aef78fc..bbd1df6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPackageSubject.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexClass;
import java.util.List;
@@ -20,12 +21,12 @@
@Override
public boolean isRenamed() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmPackage is renamed");
}
@Override
public boolean isSynthetic() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmPackage is synthetic");
}
@Override
@@ -87,4 +88,14 @@
public List<ClassSubject> getReturnTypesInProperties() {
return null;
}
+
+ @Override
+ public List<KmTypeAliasSubject> getTypeAliases() {
+ return null;
+ }
+
+ @Override
+ public KmTypeAliasSubject kmTypeAliasWithUniqueName(String name) {
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
index d9c7d55..59773c5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmPropertySubject.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.errors.Unreachable;
import kotlinx.metadata.jvm.JvmFieldSignature;
import kotlinx.metadata.jvm.JvmMethodSignature;
@@ -15,17 +16,17 @@
@Override
public boolean isRenamed() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmProperty is renamed");
}
@Override
public boolean isSynthetic() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmProperty is synthetic");
}
@Override
public boolean isExtension() {
- return false;
+ throw new Unreachable("Cannot determine if an absent KmProperty is extension");
}
@Override
@@ -47,4 +48,9 @@
public JvmMethodSignature setterSignature() {
return null;
}
+
+ @Override
+ public KmTypeSubject returnType() {
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
new file mode 100644
index 0000000..78ac796
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
@@ -0,0 +1,51 @@
+// 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.codeinspector;
+
+import com.android.tools.r8.errors.Unreachable;
+import java.util.List;
+
+public class AbsentKmTypeAliasSubject extends KmTypeAliasSubject {
+
+ @Override
+ public boolean isPresent() {
+ return false;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ throw new Unreachable("Cannot determine if an absent KmTypeAlias is renamed");
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ throw new Unreachable("Cannot determine if an absent KmTypeAlias is synthetic");
+ }
+
+ @Override
+ public String name() {
+ return null;
+ }
+
+ @Override
+ public List<KmTypeParameterSubject> typeParameters() {
+ return null;
+ }
+
+ @Override
+ public String descriptor(String pkg) {
+ return null;
+ }
+
+ @Override
+ public KmTypeSubject expandedType() {
+ return null;
+ }
+
+ @Override
+ public KmTypeSubject underlyingType() {
+ return null;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java
new file mode 100644
index 0000000..9bb0998
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeParameterSubject.java
@@ -0,0 +1,47 @@
+// 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.codeinspector;
+
+import com.android.tools.r8.errors.Unreachable;
+import java.util.List;
+import kotlinx.metadata.KmVariance;
+
+public class AbsentKmTypeParameterSubject extends KmTypeParameterSubject {
+
+ @Override
+ public boolean isPresent() {
+ return false;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ throw new Unreachable("Cannot determine if an absent KmPropertyParameter is renamed");
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ throw new Unreachable("Cannot determine if an absent KmPropertyParameter is synthetic");
+ }
+
+ @Override
+ public int getId() {
+ return 0;
+ }
+
+ @Override
+ public int getFlags() {
+ return 0;
+ }
+
+ @Override
+ public KmVariance getVariance() {
+ return null;
+ }
+
+ @Override
+ public List<KmTypeSubject> upperBounds() {
+ return null;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index 71b30cb..a2185b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -10,9 +10,11 @@
import java.util.stream.Collectors;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmDeclarationContainer;
+import kotlinx.metadata.KmTypeParameter;
public class FoundKmClassSubject extends KmClassSubject
implements FoundKmDeclarationContainerSubject {
+
private final CodeInspector codeInspector;
private final DexClass clazz;
private final KmClass kmClass;
@@ -115,4 +117,14 @@
public String getCompanionObject() {
return kmClass.getCompanionObject();
}
+
+ @Override
+ public List<KmTypeParameter> getKmTypeParameters() {
+ return kmClass.getTypeParameters();
+ }
+
+ @Override
+ public CodeInspector getCodeInspector() {
+ return codeInspector;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
index 626388f..6363747 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmDeclarationContainerSubject.java
@@ -18,6 +18,7 @@
import kotlinx.metadata.KmPropertyExtensionVisitor;
import kotlinx.metadata.KmPropertyVisitor;
import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeAlias;
import kotlinx.metadata.jvm.JvmFieldSignature;
import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
import kotlinx.metadata.jvm.JvmMethodSignature;
@@ -245,4 +246,22 @@
.filter(ClassSubject::isPresent)
.collect(Collectors.toList());
}
+
+ @Override
+ default List<KmTypeAliasSubject> getTypeAliases() {
+ CodeInspector inspector = codeInspector();
+ return getKmDeclarationContainer().getTypeAliases().stream()
+ .map(typeAlias -> new FoundKmTypeAliasSubject(inspector, typeAlias))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ default KmTypeAliasSubject kmTypeAliasWithUniqueName(String name) {
+ for (KmTypeAlias typeAlias : getKmDeclarationContainer().getTypeAliases()) {
+ if (typeAlias.getName().equals(name)) {
+ return new FoundKmTypeAliasSubject(codeInspector(), typeAlias);
+ }
+ }
+ return new AbsentKmTypeAliasSubject();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
index f86f718..93a2d96 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmFunctionSubject.java
@@ -8,9 +8,11 @@
import java.util.stream.Collectors;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
import kotlinx.metadata.jvm.JvmMethodSignature;
public class FoundKmFunctionSubject extends KmFunctionSubject {
+
private final CodeInspector codeInspector;
private final KmFunction kmFunction;
private final JvmMethodSignature signature;
@@ -68,4 +70,14 @@
public KmTypeSubject returnType() {
return new KmTypeSubject(codeInspector, kmFunction.getReturnType());
}
+
+ @Override
+ public List<KmTypeParameter> getKmTypeParameters() {
+ return kmFunction.getTypeParameters();
+ }
+
+ @Override
+ public CodeInspector getCodeInspector() {
+ return codeInspector;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
index d6bab2d..accf1b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmPropertySubject.java
@@ -9,6 +9,7 @@
import kotlinx.metadata.jvm.JvmMethodSignature;
public class FoundKmPropertySubject extends KmPropertySubject {
+
private final CodeInspector codeInspector;
private final KmProperty kmProperty;
private final JvmFieldSignature fieldSignature;
@@ -67,4 +68,9 @@
public JvmMethodSignature setterSignature() {
return setterSignature;
}
+
+ @Override
+ public KmTypeSubject returnType() {
+ return new KmTypeSubject(codeInspector, kmProperty.getReturnType());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
new file mode 100644
index 0000000..a07125c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
@@ -0,0 +1,65 @@
+// 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.codeinspector;
+
+import static com.android.tools.r8.utils.DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmTypeAlias;
+
+public class FoundKmTypeAliasSubject extends KmTypeAliasSubject {
+
+ private final KmTypeAlias kmTypeAlias;
+ private final CodeInspector codeInspector;
+
+ FoundKmTypeAliasSubject(CodeInspector codeInspector, KmTypeAlias kmTypeAlias) {
+ this.codeInspector = codeInspector;
+ this.kmTypeAlias = kmTypeAlias;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ return false;
+ }
+
+ @Override
+ public String name() {
+ return kmTypeAlias.getName();
+ }
+
+ @Override
+ public List<KmTypeParameterSubject> typeParameters() {
+ return kmTypeAlias.getTypeParameters().stream()
+ .map(kmTypeParameter -> new FoundKmTypeParameterSubject(codeInspector, kmTypeParameter))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public String descriptor(String pkg) {
+ return "L" + getBinaryNameFromJavaType(pkg) + DESCRIPTOR_PACKAGE_SEPARATOR + name() + ";";
+ }
+
+ @Override
+ public KmTypeSubject expandedType() {
+ return new KmTypeSubject(codeInspector, kmTypeAlias.expandedType);
+ }
+
+ @Override
+ public KmTypeSubject underlyingType() {
+ return new KmTypeSubject(codeInspector, kmTypeAlias.underlyingType);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
new file mode 100644
index 0000000..fb5e909
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeParameterSubject.java
@@ -0,0 +1,78 @@
+package com.android.tools.r8.utils.codeinspector;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmVariance;
+
+public class FoundKmTypeParameterSubject extends KmTypeParameterSubject {
+
+ private final CodeInspector codeInspector;
+ private final KmTypeParameter kmTypeParameter;
+
+ public FoundKmTypeParameterSubject(CodeInspector codeInspector, KmTypeParameter kmTypeParameter) {
+ this.codeInspector = codeInspector;
+ this.kmTypeParameter = kmTypeParameter;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ return false;
+ }
+
+ @Override
+ public int getId() {
+ return kmTypeParameter.getId();
+ }
+
+ @Override
+ public int getFlags() {
+ return kmTypeParameter.getFlags();
+ }
+
+ @Override
+ public KmVariance getVariance() {
+ return kmTypeParameter.getVariance();
+ }
+
+ @Override
+ public List<KmTypeSubject> upperBounds() {
+ return kmTypeParameter.getUpperBounds().stream()
+ .map(kmType -> new KmTypeSubject(codeInspector, kmType))
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof FoundKmTypeParameterSubject)) {
+ return false;
+ }
+ KmTypeParameter other = ((FoundKmTypeParameterSubject) obj).kmTypeParameter;
+ if (!kmTypeParameter.getName().equals(other.getName())
+ || kmTypeParameter.getId() != other.getId()
+ || kmTypeParameter.getFlags() != other.getFlags()
+ || kmTypeParameter.getVariance() != other.getVariance()) {
+ return false;
+ }
+ if (kmTypeParameter.getUpperBounds().size() != other.getUpperBounds().size()) {
+ return false;
+ }
+ for (int i = 0; i < kmTypeParameter.getUpperBounds().size(); i++) {
+ if (!KmTypeSubject.areEqual(
+ kmTypeParameter.getUpperBounds().get(i), other.getUpperBounds().get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index 3f5be34..753f02a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -6,7 +6,8 @@
import com.android.tools.r8.graph.DexClass;
import java.util.List;
-public abstract class KmClassSubject extends Subject implements KmDeclarationContainerSubject {
+public abstract class KmClassSubject extends Subject
+ implements KmDeclarationContainerSubject, KmTypeParameterSubjectMixin {
public abstract String getName();
public abstract DexClass getDexClass();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassifierSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassifierSubject.java
new file mode 100644
index 0000000..8adb293
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassifierSubject.java
@@ -0,0 +1,47 @@
+// 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.codeinspector;
+
+import kotlinx.metadata.KmClassifier;
+
+public class KmClassifierSubject extends Subject {
+
+ private final KmClassifier classifier;
+
+ public KmClassifierSubject(KmClassifier classifier) {
+ this.classifier = classifier;
+ }
+
+ public boolean isTypeParameter() {
+ return classifier instanceof KmClassifier.TypeParameter;
+ }
+
+ public KmClassifier.TypeParameter asTypeParameter() {
+ return (KmClassifier.TypeParameter) classifier;
+ }
+
+ public boolean isTypeAlias() {
+ return classifier instanceof KmClassifier.TypeAlias;
+ }
+
+ public KmClassifier.TypeAlias asTypeAlias() {
+ return (KmClassifier.TypeAlias) classifier;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ return false;
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ return false;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
index 0893077..24e26da 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmDeclarationContainerSubject.java
@@ -29,4 +29,8 @@
List<KmPropertySubject> getProperties();
List<ClassSubject> getReturnTypesInProperties();
+
+ List<KmTypeAliasSubject> getTypeAliases();
+
+ KmTypeAliasSubject kmTypeAliasWithUniqueName(String name);
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
index 161aa3f..0f9dae1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmFunctionSubject.java
@@ -7,7 +7,7 @@
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.jvm.JvmMethodSignature;
-public abstract class KmFunctionSubject extends Subject {
+public abstract class KmFunctionSubject extends Subject implements KmTypeParameterSubjectMixin {
// TODO(b/145824437): This is a dup of KotlinMetadataSynthesizer#isExtension
static boolean isExtension(KmFunction kmFunction) {
return kmFunction.getReceiverParameterType() != null;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
index 826c1f1..dc3f62e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmPropertySubject.java
@@ -22,4 +22,6 @@
public abstract JvmMethodSignature getterSignature();
public abstract JvmMethodSignature setterSignature();
+
+ public abstract KmTypeSubject returnType();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
new file mode 100644
index 0000000..f87d368
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
@@ -0,0 +1,20 @@
+// 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.codeinspector;
+
+import java.util.List;
+
+public abstract class KmTypeAliasSubject extends Subject {
+
+ public abstract String name();
+
+ public abstract List<KmTypeParameterSubject> typeParameters();
+
+ public abstract String descriptor(String pkg);
+
+ public abstract KmTypeSubject expandedType();
+
+ public abstract KmTypeSubject underlyingType();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java
new file mode 100644
index 0000000..672ad09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubject.java
@@ -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.utils.codeinspector;
+
+import java.util.List;
+import kotlinx.metadata.KmVariance;
+
+public abstract class KmTypeParameterSubject extends Subject {
+
+ public abstract int getId();
+
+ public abstract int getFlags();
+
+ public abstract KmVariance getVariance();
+
+ public abstract List<KmTypeSubject> upperBounds();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubjectMixin.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubjectMixin.java
new file mode 100644
index 0000000..257c109
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeParameterSubjectMixin.java
@@ -0,0 +1,34 @@
+// 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.codeinspector;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import kotlinx.metadata.KmTypeParameter;
+
+public interface KmTypeParameterSubjectMixin {
+
+ List<KmTypeParameter> getKmTypeParameters();
+
+ CodeInspector getCodeInspector();
+
+ default List<KmTypeParameterSubject> typeParameters() {
+ CodeInspector codeInspector = getCodeInspector();
+ return getKmTypeParameters().stream()
+ .map(kmTypeParam -> new FoundKmTypeParameterSubject(codeInspector, kmTypeParam))
+ .collect(Collectors.toList());
+ }
+
+ default KmTypeParameterSubject kmTypeParameterWithUniqueName(String name) {
+ FoundKmTypeParameterSubject typeSubject = null;
+ for (KmTypeParameter kmTypeParameter : getKmTypeParameters()) {
+ if (kmTypeParameter.getName().equals(name)) {
+ assert typeSubject == null;
+ typeSubject = new FoundKmTypeParameterSubject(getCodeInspector(), kmTypeParameter);
+ }
+ }
+ return typeSubject != null ? typeSubject : new AbsentKmTypeParameterSubject();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
index 790ecd2..76f3782 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeProjectionSubject.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.errors.Unreachable;
import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmVariance;
public class KmTypeProjectionSubject extends Subject {
private final CodeInspector codeInspector;
@@ -19,6 +20,10 @@
return new KmTypeSubject(codeInspector, kmTypeProjection.getType());
}
+ public KmVariance variance() {
+ return kmTypeProjection.getVariance();
+ }
+
@Override
public boolean isPresent() {
return true;
@@ -33,4 +38,25 @@
public boolean isSynthetic() {
throw new Unreachable("Cannot determine if a type argument is synthetic");
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof KmTypeProjectionSubject)) {
+ return false;
+ }
+ return areEqual(this.kmTypeProjection, ((KmTypeProjectionSubject) obj).kmTypeProjection);
+ }
+
+ public static boolean areEqual(KmTypeProjection one, KmTypeProjection other) {
+ if (one == null && other == null) {
+ return true;
+ }
+ if (one == null || other == null) {
+ return false;
+ }
+ if (one.getVariance() != other.getVariance()) {
+ return false;
+ }
+ return KmTypeSubject.areEqual(one.getType(), other.getType());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
index d671a7d..44a141a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeSubject.java
@@ -10,8 +10,10 @@
import com.android.tools.r8.utils.Box;
import java.util.List;
import java.util.stream.Collectors;
+import kotlinx.metadata.KmAnnotation;
import kotlinx.metadata.KmType;
import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
public class KmTypeSubject extends Subject {
private final CodeInspector codeInspector;
@@ -57,6 +59,10 @@
.collect(Collectors.toList());
}
+ public KmClassifierSubject classifier() {
+ return new KmClassifierSubject(kmType.classifier);
+ }
+
@Override
public boolean isPresent() {
return true;
@@ -72,4 +78,66 @@
public boolean isSynthetic() {
throw new Unreachable("Cannot determine if a type is synthetic");
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof KmTypeSubject)) {
+ return false;
+ }
+ return areEqual(this.kmType, ((KmTypeSubject) obj).kmType);
+ }
+
+ public static boolean areEqual(KmType one, KmType other) {
+ if (one == null && other == null) {
+ return true;
+ }
+ if (one == null || other == null) {
+ return false;
+ }
+ if (one.getFlags() != other.getFlags()) {
+ return false;
+ }
+ if (!one.classifier.toString().equals(other.classifier.toString())) {
+ return false;
+ }
+ if (one.getArguments().size() != other.getArguments().size()) {
+ return false;
+ }
+ for (int i = 0; i < one.getArguments().size(); i++) {
+ if (!KmTypeProjectionSubject.areEqual(
+ one.getArguments().get(i), other.getArguments().get(i))) {
+ return false;
+ }
+ }
+ if (!areEqual(one.getAbbreviatedType(), other.getAbbreviatedType())) {
+ return false;
+ }
+ if (!areEqual(one.getOuterType(), other.getOuterType())) {
+ return false;
+ }
+ // TODO(b/152745540): Add equality for flexibleUpperBoundType.
+ if (JvmExtensionsKt.isRaw(one) != JvmExtensionsKt.isRaw(other)) {
+ return false;
+ }
+ List<KmAnnotation> annotationsOne = JvmExtensionsKt.getAnnotations(one);
+ List<KmAnnotation> annotationsOther = JvmExtensionsKt.getAnnotations(other);
+ if (annotationsOne.size() != annotationsOther.size()) {
+ return false;
+ }
+ for (int i = 0; i < annotationsOne.size(); i++) {
+ KmAnnotation kmAnnotationOne = annotationsOne.get(i);
+ KmAnnotation kmAnnotationOther = annotationsOther.get(i);
+ if (!kmAnnotationOne.getClassName().equals(kmAnnotationOther.getClassName())) {
+ return false;
+ }
+ if (!kmAnnotationOne
+ .getArguments()
+ .keySet()
+ .equals(kmAnnotationOther.getArguments().keySet())) {
+ return false;
+ }
+ assert false : "Not defined how to compare kmAnnotationArguments";
+ }
+ return true;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 3bbea86..949903d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.naming.retrace.StackTrace;
import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
import com.android.tools.r8.references.MethodReference;
@@ -40,6 +41,10 @@
type = "@Metadata.KmFunction";
} else if (subject instanceof KmPropertySubject) {
type = "@Metadata.KmProperty";
+ } else if (subject instanceof KmTypeParameterSubject) {
+ type = "@Metadata.KmTypeParameter";
+ } else if (subject instanceof KmClassifierSubject) {
+ type = "@Metadata.KmClassifier";
}
return type;
}
@@ -62,6 +67,10 @@
name = ((KmFunctionSubject) subject).toString();
} else if (subject instanceof KmPropertySubject) {
name = ((KmPropertySubject) subject).toString();
+ } else if (subject instanceof KmTypeParameterSubject) {
+ name = ((KmTypeParameterSubject) subject).getId() + "";
+ } else if (subject instanceof KmClassifierSubject) {
+ name = subject.toString();
}
return name;
}
@@ -394,6 +403,31 @@
};
}
+ public static Matcher<KmTypeSubject> isDexClass(DexClass clazz) {
+ return new TypeSafeMatcher<KmTypeSubject>() {
+ @Override
+ protected boolean matchesSafely(KmTypeSubject item) {
+ String descriptor = item.descriptor();
+ if (descriptor == null) {
+ return false;
+ }
+ return descriptor.equals(clazz.type.toDescriptorString());
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is class");
+ }
+
+ @Override
+ protected void describeMismatchSafely(KmTypeSubject item, Description mismatchDescription) {
+ mismatchDescription
+ .appendText(item.descriptor())
+ .appendText(" is not " + clazz.type.toDescriptorString());
+ }
+ };
+ }
+
public static Matcher<RetraceMethodResult> isInlineFrame() {
return new TypeSafeMatcher<RetraceMethodResult>() {
@Override
diff --git a/tools/asmifier.py b/tools/asmifier.py
index 094fd71..c2fbdc0 100755
--- a/tools/asmifier.py
+++ b/tools/asmifier.py
@@ -10,13 +10,17 @@
import sys
import utils
+ASM_VERSION = '7.2'
+ASM_JAR = 'asm-' + ASM_VERSION + '.jar'
+ASM_UTIL_JAR = 'asm-util-' + ASM_VERSION + '.jar'
+
def run(args, build=True):
if build:
gradle.RunGradle(['copyMavenDeps'])
cmd = []
cmd.append(jdk.GetJavaExecutable())
- cp = ":".join([os.path.join(utils.REPO_ROOT, 'build/deps/asm-7.1.jar'),
- os.path.join(utils.REPO_ROOT, 'build/deps/asm-util-7.1.jar')])
+ cp = ":".join([os.path.join(utils.REPO_ROOT, 'build/deps/' + ASM_JAR),
+ os.path.join(utils.REPO_ROOT, 'build/deps/' + ASM_UTIL_JAR)])
cmd.extend(['-cp', cp])
cmd.append('org.objectweb.asm.util.ASMifier')
cmd.extend(args)
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 6bfb5cb..5aa5f97 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -51,6 +51,11 @@
default=False,
action='store_true')
parser.add_argument(
+ '--printtimes',
+ help='Print timing information from r8',
+ default=False,
+ action='store_true')
+ parser.add_argument(
'--ea',
help='Enable Java assertions when running the compiler (default disabled)',
default=False,
@@ -165,6 +170,8 @@
'-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005')
if args.ea:
cmd.append('-ea')
+ if args.printtimes:
+ cmd.append('-Dcom.android.tools.r8.printtimes=1')
cmd.extend(['-cp', '%s:%s' % (wrapper_dir, jar)])
if compiler == 'd8':
cmd.append('com.android.tools.r8.D8')
@@ -180,6 +187,8 @@
cmd.extend(['--classpath', dump.classpath_jar()])
if compiler != 'd8' and dump.config_file():
cmd.extend(['--pg-conf', dump.config_file()])
+ if compiler != 'd8':
+ cmd.extend(['--pg-map-output', '%s.map' % out])
cmd.extend(otherargs)
utils.PrintCmd(cmd)
try:
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 11e4c8b..b98be86 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -70,15 +70,15 @@
'find-xmx-min': 256,
'find-xmx-max': 450,
'find-xmx-range': 16,
- 'oom-threshold': 380,
+ 'oom-threshold': 335,
},
{
'app': 'youtube',
'version': '12.22',
- 'find-xmx-min': 800,
- 'find-xmx-max': 1200,
+ 'find-xmx-min': 750,
+ 'find-xmx-max': 1150,
'find-xmx-range': 32,
- 'oom-threshold': 1037,
+ 'oom-threshold': 950,
# TODO(b/143431825): Youtube can OOM randomly in memory configurations
# that should work.
'skip-find-xmx-max': True,
diff --git a/tools/retrace_benchmark.py b/tools/retrace_benchmark.py
index 5e2bd3b..21bb738 100755
--- a/tools/retrace_benchmark.py
+++ b/tools/retrace_benchmark.py
@@ -64,8 +64,7 @@
os.path.join(utils.THIRD_PARTY, 'retrace_benchmark', 'stacktrace.txt')]
utils.PrintCmd(retrace_args)
t0 = time.time()
- subprocess.check_call(
- retrace_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ subprocess.check_call(retrace_args)
t1 = time.time()
if options.print_runtimeraw:
print('{}(RunTimeRaw): {} ms'
@@ -80,4 +79,3 @@
utils.check_java_version()
with utils.TempDir() as temp:
run_retrace(options, temp)
-