Merge commit 'b02372be4e2142e2ae4922edfe4ffb1953bca294' into dev-release
diff --git a/.gitignore b/.gitignore
index b19c671..bd447f4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,8 @@
third_party/android_jar/lib-v[0-9][0-9]
third_party/android_jar/lib-v[0-9][0-9].tar.gz
third_party/android_jar/lib.tar.gz
+third_party/android_jar/libcore_latest
+third_party/android_jar/libcore_latest.tar.gz
third_party/android_jar/api-versions.tar.gz
third_party/android_jar/api-versions
third_party/android_sdk
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 088b42f..87fd416 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -15,6 +15,9 @@
'scripts',
'google-java-format-diff.py')
+FMT_CMD_JDK17 = path.join('tools','google-java-format-diff.py')
+
+
def CheckDoNotMerge(input_api, output_api):
for l in input_api.change.FullDescriptionText().splitlines():
if l.lower().startswith('do not merge'):
@@ -47,7 +50,17 @@
or bypass the checks with:
git cl upload --bypass-hooks
- """ % (FMT_CMD, FMT_CMD)))
+
+If formatting fails with 'No enum constant javax.lang.model.element.Modifier.SEALED' try
+
+ git diff -U0 $(git cl upstream) | %s %s %s -p1 -i && git commit -a --amend --no-edit && git cl upload
+ """ % (
+ FMT_CMD,
+ FMT_CMD,
+ FMT_CMD_JDK17,
+ '--google-java-format-jar',
+ 'third_party/google/google-java-format/1.14.0/google-java-format-1.14.0-all-deps.jar'
+)))
return results
def CheckDeterministicDebuggingChanged(input_api, output_api, branch):
diff --git a/build.gradle b/build.gradle
index 21a20be..9b2772c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -303,6 +303,7 @@
],
"third_party": [
"android_cts_baseline",
+ "android_jar/libcore_latest",
"android_jar/lib-v14",
"android_jar/lib-v15",
"android_jar/lib-v19",
@@ -460,7 +461,8 @@
"protobuf-lite",
"retrace_internal",
"youtube/youtube.android_15.33",
- "youtube/youtube.android_16.20"
+ "youtube/youtube.android_16.20",
+ "youtube/youtube.android_17.19"
],
]
@@ -2274,6 +2276,9 @@
if (!project.hasProperty('all_tests')) {
exclude "com/android/tools/r8/art/dx/**"
}
+ if (project.hasProperty('no_arttests')) {
+ exclude "com/android/tools/r8/art/**"
+ }
if (project.hasProperty('shard_count') ) {
assert project.hasProperty('shard_number')
int shard_count = project.getProperty('shard_count') as Integer
diff --git a/infra/config/global/generated/cr-buildbucket.cfg b/infra/config/global/generated/cr-buildbucket.cfg
index 0323612..ca4bc25 100644
--- a/infra/config/global/generated/cr-buildbucket.cfg
+++ b/infra/config/global/generated/cr-buildbucket.cfg
@@ -97,6 +97,7 @@
' "--one_line_per_test",'
' "--archive_failures",'
' "--no_internal",'
+ ' "--no_arttests",'
' "--desugared-library",'
' "HEAD"'
' ]'
@@ -132,6 +133,7 @@
' "--one_line_per_test",'
' "--archive_failures",'
' "--no_internal",'
+ ' "--no_arttests",'
' "--desugared-library",'
' "HEAD",'
' "--desugared-library-configuration",'
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg
index 6153ae8..53f90a7 100644
--- a/infra/config/global/generated/project.cfg
+++ b/infra/config/global/generated/project.cfg
@@ -7,7 +7,7 @@
name: "r8"
access: "group:all"
lucicfg {
- version: "1.30.10"
+ version: "1.30.11"
package_dir: ".."
config_dir: "generated"
entry_point: "main.star"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 4949fc2..d1529cf 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -342,6 +342,7 @@
"--one_line_per_test",
"--archive_failures",
"--no_internal",
+ "--no_arttests",
"--desugared-library",
"HEAD"
]
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 9393f4d..9a340b8 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -15,12 +15,14 @@
import com.android.tools.r8.graph.AppServices;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.LazyLoadedDexApplication;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.analysis.ClassInitializerAssertionEnablingAnalysis;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.inspector.internal.InspectorImpl;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.TypeRewriter;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryAmender;
@@ -34,6 +36,7 @@
import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.origin.CommandLineOrigin;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.synthesis.SyntheticFinalization;
import com.android.tools.r8.synthesis.SyntheticItems;
@@ -211,6 +214,9 @@
timing.begin("Read input app");
AppView<AppInfo> appView = readApp(inputApp, options, executor, timing);
timing.end();
+ timing.begin("Initialize assume info collection");
+ initializeAssumeInfoCollection(appView);
+ timing.end();
timing.begin("Desugared library amend");
DesugaredLibraryAmender.run(appView);
timing.end();
@@ -343,6 +349,22 @@
}
}
+ private static void initializeAssumeInfoCollection(AppView<AppInfo> appView) {
+ AssumeInfoCollection.Builder assumeInfoCollectionBuilder = AssumeInfoCollection.builder();
+ AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ InternalOptions options = appView.options();
+ if (options.isGeneratingDex()) {
+ assumeInfoCollectionBuilder
+ .meetAssumeValue(
+ dexItemFactory.androidOsBuildVersionMembers.SDK_INT,
+ abstractValueFactory.createNumberFromIntervalValue(
+ options.getMinApiLevel().getLevel(), Integer.MAX_VALUE))
+ .setIsSideEffectFree(dexItemFactory.androidOsBuildVersionMembers.SDK_INT);
+ }
+ appView.setAssumeInfoCollection(assumeInfoCollectionBuilder.build());
+ }
+
private static void finalizeApplication(AppView<AppInfo> appView, ExecutorService executorService)
throws ExecutionException {
SyntheticFinalization.finalize(appView, executorService);
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 9dc5143..af340d4 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -205,6 +205,7 @@
null,
null,
Collections.emptyList(),
+ Collections.emptyList(),
null,
Collections.emptyList(),
ClassSignature.noSignature(),
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5d07a1c..d70426f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -79,6 +79,7 @@
import com.android.tools.r8.shaking.AbstractMethodRemover;
import com.android.tools.r8.shaking.AnnotationRemover;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
import com.android.tools.r8.shaking.ClassInitFieldSynthesizer;
import com.android.tools.r8.shaking.DefaultTreePrunerConfiguration;
import com.android.tools.r8.shaking.DiscardedChecker;
@@ -338,6 +339,7 @@
options.itemFactory, options.getMinApiLevel()));
}
}
+ AssumeInfoCollection.Builder assumeInfoCollectionBuilder = AssumeInfoCollection.builder();
SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
appView.setRootSet(
RootSet.builder(
@@ -345,7 +347,9 @@
subtypingInfo,
Iterables.concat(
options.getProguardConfiguration().getRules(), synthesizedProguardRules))
+ .setAssumeInfoCollectionBuilder(assumeInfoCollectionBuilder)
.build(executorService));
+ appView.setAssumeInfoCollection(assumeInfoCollectionBuilder.build());
// Compute the main dex rootset that will be the base of first and final main dex tracing
// before building a new appview with only live classes (and invalidating subtypingInfo).
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4e7ca32..8d323ab 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardConfigurationParserOptions;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.shaking.ProguardConfigurationSource;
import com.android.tools.r8.shaking.ProguardConfigurationSourceBytes;
@@ -116,8 +117,8 @@
private boolean skipDump = false;
private boolean enableMissingLibraryApiModeling = false;
- private boolean allowTestProguardOptions =
- System.getProperty("com.android.tools.r8.allowTestProguardOptions") != null;
+ private final ProguardConfigurationParserOptions.Builder parserOptionsBuilder =
+ ProguardConfigurationParserOptions.builder().readEnvironment();
// TODO(zerny): Consider refactoring CompatProguardCommandBuilder to avoid subclassing.
Builder() {
@@ -520,7 +521,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(
- factory, reporter, inputDependencyGraphConsumer, allowTestProguardOptions);
+ factory, reporter, parserOptionsBuilder.build(), inputDependencyGraphConsumer);
if (!proguardConfigs.isEmpty()) {
parser.parse(proguardConfigs);
}
@@ -662,9 +663,21 @@
}
+ void setEnableExperimentalCheckEnumUnboxed() {
+ parserOptionsBuilder.setEnableExperimentalCheckEnumUnboxed(true);
+ }
+
+ void setEnableExperimentalConvertCheckNotNull() {
+ parserOptionsBuilder.setEnableExperimentalConvertCheckNotNull(true);
+ }
+
+ void setEnableExperimentalWhyAreYouNotInlining() {
+ parserOptionsBuilder.setEnableExperimentalWhyAreYouNotInlining(true);
+ }
+
// Internal for-testing method to allow proguard options only available for testing.
- void allowTestProguardOptions() {
- allowTestProguardOptions = true;
+ void setEnableTestProguardOptions() {
+ parserOptionsBuilder.setEnableTestingOptions(true);
}
}
diff --git a/src/main/java/com/android/tools/r8/androidapi/CovariantReturnTypeMethods.java b/src/main/java/com/android/tools/r8/androidapi/CovariantReturnTypeMethods.java
new file mode 100644
index 0000000..077c1be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/androidapi/CovariantReturnTypeMethods.java
@@ -0,0 +1,289 @@
+// Copyright (c) 2022, 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.
+
+// ***********************************************************************************
+// GENERATED FILE. DO NOT EDIT! See GenerateCovariantReturnTypeMethodsTest.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.androidapi;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import java.util.function.Consumer;
+
+public class CovariantReturnTypeMethods {
+ public static void registerMethodsWithCovariantReturnType(
+ DexItemFactory factory, Consumer<DexMethod> consumer) {
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ByteBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+ "clear"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ByteBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+ "flip"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ByteBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/ByteBuffer;"), factory.createType("I")),
+ "limit"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ByteBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+ "mark"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ByteBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/ByteBuffer;"), factory.createType("I")),
+ "position"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ByteBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+ "reset"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ByteBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ByteBuffer;")),
+ "rewind"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/CharBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+ "clear"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/CharBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+ "flip"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/CharBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/CharBuffer;"), factory.createType("I")),
+ "limit"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/CharBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+ "mark"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/CharBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/CharBuffer;"), factory.createType("I")),
+ "position"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/CharBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+ "reset"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/CharBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/CharBuffer;")),
+ "rewind"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/DoubleBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+ "clear"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/DoubleBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+ "flip"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/DoubleBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/DoubleBuffer;"), factory.createType("I")),
+ "limit"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/DoubleBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+ "mark"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/DoubleBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/DoubleBuffer;"), factory.createType("I")),
+ "position"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/DoubleBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+ "reset"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/DoubleBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/DoubleBuffer;")),
+ "rewind"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/FloatBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+ "clear"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/FloatBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+ "flip"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/FloatBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/FloatBuffer;"), factory.createType("I")),
+ "limit"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/FloatBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+ "mark"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/FloatBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/FloatBuffer;"), factory.createType("I")),
+ "position"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/FloatBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+ "reset"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/FloatBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/FloatBuffer;")),
+ "rewind"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/IntBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+ "clear"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/IntBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+ "flip"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/IntBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/IntBuffer;"), factory.createType("I")),
+ "limit"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/IntBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+ "mark"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/IntBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/IntBuffer;"), factory.createType("I")),
+ "position"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/IntBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+ "reset"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/IntBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/IntBuffer;")),
+ "rewind"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/LongBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+ "clear"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/LongBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+ "flip"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/LongBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/LongBuffer;"), factory.createType("I")),
+ "limit"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/LongBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+ "mark"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/LongBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/LongBuffer;"), factory.createType("I")),
+ "position"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/LongBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+ "reset"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/LongBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/LongBuffer;")),
+ "rewind"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ShortBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+ "clear"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ShortBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+ "flip"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ShortBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/ShortBuffer;"), factory.createType("I")),
+ "limit"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ShortBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+ "mark"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ShortBuffer;"),
+ factory.createProto(
+ factory.createType("Ljava/nio/ShortBuffer;"), factory.createType("I")),
+ "position"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ShortBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+ "reset"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/nio/ShortBuffer;"),
+ factory.createProto(factory.createType("Ljava/nio/ShortBuffer;")),
+ "rewind"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/time/LocalDate;"),
+ factory.createProto(factory.createType("Ljava/time/chrono/IsoEra;")),
+ "getEra"));
+ consumer.accept(
+ factory.createMethod(
+ factory.createType("Ljava/util/concurrent/ConcurrentHashMap;"),
+ factory.createProto(
+ factory.createType("Ljava/util/concurrent/ConcurrentHashMap$KeySetView;")),
+ "keySet"));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
index 78b9067..deb3f3b 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLength.java
@@ -88,8 +88,6 @@
DexItemFactory dexItemFactory) {
// ..., arrayref →
// ..., length
- return frame
- .popInitialized(appView, dexItemFactory.objectArrayType)
- .push(config, dexItemFactory.intType);
+ return frame.popArray(appView).push(config, dexItemFactory.intType);
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
index 8bba186..2d719fd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoad.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
@@ -24,17 +23,13 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
-import com.android.tools.r8.utils.structural.CompareToVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-public class CfArrayLoad extends CfInstruction {
-
- private final MemberType type;
+public class CfArrayLoad extends CfArrayLoadOrStore {
public CfArrayLoad(MemberType type) {
- assert type.isPrecise();
- this.type = type;
+ super(type);
}
@Override
@@ -42,18 +37,8 @@
return getLoadType();
}
- @Override
- public int internalAcceptCompareTo(
- CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
- return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
- }
-
- public MemberType getType() {
- return type;
- }
-
private int getLoadType() {
- switch (type) {
+ switch (getType()) {
case OBJECT:
return Opcodes.AALOAD;
case BOOLEAN_OR_BYTE:
@@ -71,7 +56,7 @@
case DOUBLE:
return Opcodes.DALOAD;
default:
- throw new Unreachable("Unexpected type " + type);
+ throw new Unreachable("Unexpected type " + getType());
}
}
@@ -89,34 +74,24 @@
}
@Override
- public int bytecodeSizeUpperBound() {
- return 1;
- }
-
- @Override
public void print(CfPrinter printer) {
printer.print(this);
}
@Override
- public boolean canThrow() {
- return true;
- }
-
- @Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
Slot index = state.pop();
Slot array = state.pop();
Slot value;
assert array.type.isObject();
- ValueType memberType = ValueType.fromMemberType(type);
+ ValueType memberType = ValueType.fromMemberType(getType());
if (array.preciseType != null) {
value = state.push(array.preciseType.toArrayElementType(builder.appView.dexItemFactory()));
assert state.peek().type == memberType;
} else {
value = state.push(memberType);
}
- builder.addArrayGet(type, value.register, array.register, index.register);
+ builder.addArrayGet(getType(), value.register, array.register, index.register);
}
@Override
@@ -135,7 +110,16 @@
// ..., value
return frame
.popInitialized(appView, dexItemFactory.intType)
- .popInitialized(appView, dexItemFactory.objectArrayType)
- .push(appView, config, type);
+ .popInitialized(
+ appView,
+ getExpectedArrayType(dexItemFactory),
+ (state, head) ->
+ head.isNullType()
+ ? state.push(appView, config, getType())
+ : state.push(
+ config,
+ head.asInitializedReferenceType()
+ .getInitializedType()
+ .toArrayElementType(dexItemFactory)));
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayLoadOrStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoadOrStore.java
new file mode 100644
index 0000000..93dd67a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayLoadOrStore.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2022, 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.code;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCompareHelper;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.utils.structural.CompareToVisitor;
+
+public abstract class CfArrayLoadOrStore extends CfInstruction {
+
+ private final MemberType type;
+
+ CfArrayLoadOrStore(MemberType type) {
+ assert type.isPrecise();
+ this.type = type;
+ }
+
+ @Override
+ public int bytecodeSizeUpperBound() {
+ return 1;
+ }
+
+ @Override
+ public boolean canThrow() {
+ return true;
+ }
+
+ DexType getExpectedArrayType(DexItemFactory dexItemFactory) {
+ switch (type) {
+ case OBJECT:
+ return dexItemFactory.objectArrayType;
+ case BOOLEAN_OR_BYTE:
+ return dexItemFactory.intArrayType;
+ case CHAR:
+ return dexItemFactory.charArrayType;
+ case SHORT:
+ return dexItemFactory.shortArrayType;
+ case INT:
+ return dexItemFactory.intArrayType;
+ case FLOAT:
+ return dexItemFactory.floatArrayType;
+ case LONG:
+ return dexItemFactory.longArrayType;
+ case DOUBLE:
+ return dexItemFactory.doubleArrayType;
+ default:
+ throw new Unreachable("Unexpected type: " + type);
+ }
+ }
+
+ public MemberType getType() {
+ return type;
+ }
+
+ @Override
+ public int internalAcceptCompareTo(
+ CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
+ return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
index 489285f..9435e85 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfArrayStore.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.CfCompareHelper;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.InitClassLens;
@@ -23,21 +22,13 @@
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.optimize.interfaces.analysis.CfAnalysisConfig;
import com.android.tools.r8.optimize.interfaces.analysis.CfFrameState;
-import com.android.tools.r8.utils.structural.CompareToVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-public class CfArrayStore extends CfInstruction {
-
- private final MemberType type;
+public class CfArrayStore extends CfArrayLoadOrStore {
public CfArrayStore(MemberType type) {
- assert type.isPrecise();
- this.type = type;
- }
-
- public MemberType getType() {
- return type;
+ super(type);
}
@Override
@@ -45,14 +36,8 @@
return getStoreType();
}
- @Override
- public int internalAcceptCompareTo(
- CfInstruction other, CompareToVisitor visitor, CfCompareHelper helper) {
- return CfCompareHelper.compareIdUniquelyDeterminesEquality(this, other);
- }
-
private int getStoreType() {
- switch (type) {
+ switch (getType()) {
case OBJECT:
return Opcodes.AASTORE;
case BOOLEAN_OR_BYTE:
@@ -70,7 +55,7 @@
case DOUBLE:
return Opcodes.DASTORE;
default:
- throw new Unreachable("Unexpected type " + type);
+ throw new Unreachable("Unexpected type " + getType());
}
}
@@ -88,26 +73,16 @@
}
@Override
- public int bytecodeSizeUpperBound() {
- return 1;
- }
-
- @Override
public void print(CfPrinter printer) {
printer.print(this);
}
@Override
- public boolean canThrow() {
- return true;
- }
-
- @Override
public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
Slot value = state.pop();
Slot index = state.pop();
Slot array = state.pop();
- builder.addArrayPut(type, value.register, array.register, index.register);
+ builder.addArrayPut(getType(), value.register, array.register, index.register);
}
@Override
@@ -125,8 +100,8 @@
// ..., arrayref, index, value →
// ...
return frame
- .popInitialized(appView, type)
+ .popInitialized(appView, getType())
.popInitialized(appView, dexItemFactory.intType)
- .popInitialized(appView, dexItemFactory.objectArrayType);
+ .popInitialized(appView, getExpectedArrayType(dexItemFactory));
}
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
index dc247a4..52854e4 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfAssignability.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.utils.MapUtils;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
@@ -78,6 +77,7 @@
// Rules found at https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.10.1.2
public static boolean isAssignable(DexType source, DexType target, AppView<?> appView) {
+ assert !target.isNullValueType();
DexItemFactory factory = appView.dexItemFactory();
source = byteCharShortOrBooleanToInt(source, factory);
target = byteCharShortOrBooleanToInt(target, factory);
@@ -88,23 +88,30 @@
return false;
}
// Both are now references - everything is assignable to object.
+ assert source.isReferenceType();
+ assert target.isReferenceType();
if (target == factory.objectType) {
return true;
}
// isAssignable(null, class(_, _)).
// isAssignable(null, arrayOf(_)).
- if (source == DexItemFactory.nullValueType) {
+ if (source.isNullValueType()) {
return true;
}
- if (target.isArrayType() != target.isArrayType()) {
- return false;
- }
if (target.isArrayType()) {
- return isAssignable(
- target.toArrayElementType(factory), target.toArrayElementType(factory), appView);
+ return source.isArrayType()
+ && isAssignable(
+ source.toArrayElementType(factory), target.toArrayElementType(factory), appView);
}
+ assert target.isClassType();
+ if (source.isArrayType()) {
+ // Array types are assignable to the class types Object, Cloneable and Serializable.
+ // Object is handled above, so we only need to check the other two.
+ return target == factory.cloneableType || target == factory.serializableType;
+ }
+ assert source.isClassType();
// TODO(b/166570659): Do a sub-type check that allows for missing classes in hierarchy.
- return MemberType.fromDexType(source) == MemberType.fromDexType(target);
+ return true;
}
public static boolean isAssignable(DexType source, ValueType target, AppView<?> appView) {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
index 7c73bbb..4c21ed6 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerificationHelper.java
@@ -46,12 +46,13 @@
public CfFrameVerificationHelper(
AppView<?> appView,
CfCode code,
+ GraphLens codeLens,
ProgramMethod method,
Map<CfLabel, CfFrame> stateMap,
List<CfTryCatch> tryCatchRanges) {
this.appView = appView;
this.code = code;
- this.codeLens = code.getCodeLens(appView);
+ this.codeLens = codeLens;
this.method = method;
this.previousMethod =
appView.graphLens().getOriginalMethodSignature(method.getReference(), codeLens);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index ffbd427..b67dda7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -328,10 +328,14 @@
// ...
frame = frame.popInitialized(appView, method.getParameters().getBacking());
if (opcode != Opcodes.INVOKESTATIC) {
- frame =
- opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(dexItemFactory)
- ? frame.popAndInitialize(appView, method, config)
- : frame.popInitialized(appView, method.getHolderType());
+ if (method.getHolderType().isArrayType()) {
+ frame = frame.popArray(appView);
+ } else {
+ frame =
+ opcode == Opcodes.INVOKESPECIAL && method.isInstanceInitializer(dexItemFactory)
+ ? frame.popAndInitialize(appView, method, config)
+ : frame.popInitialized(appView, method.getHolderType());
+ }
}
if (method.getReturnType().isVoidType()) {
return frame;
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
index ce9f818..392b16d 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/BaseFrameType.java
@@ -104,6 +104,11 @@
}
@Override
+ public boolean isInitializedReferenceType() {
+ return true;
+ }
+
+ @Override
public InitializedReferenceFrameType asInitializedReferenceType() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
index 6522c7e..f614d65 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/FrameType.java
@@ -57,8 +57,7 @@
static PrimitiveFrameType primitive(DexType type) {
assert type.isPrimitiveType();
- char c = (char) type.getDescriptor().content[0];
- switch (c) {
+ switch (type.getDescriptor().getFirstByteAsChar()) {
case 'Z':
return booleanType();
case 'B':
@@ -145,6 +144,8 @@
boolean isInitialized();
+ boolean isInitializedReferenceType();
+
InitializedReferenceFrameType asInitializedReferenceType();
boolean isInt();
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
index c2a2152..fd00888 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/InitializedReferenceFrameType.java
@@ -34,6 +34,11 @@
}
@Override
+ public boolean isInitializedReferenceType() {
+ return true;
+ }
+
+ @Override
public InitializedReferenceFrameType asInitializedReferenceType() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index e9b23e7..19bf467 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -29,14 +29,17 @@
public class DebugRepresentation {
+ public static final int NO_PC_ENCODING = -1;
+ public static final int ALWAYS_PC_ENCODING = Integer.MAX_VALUE;
+
public interface DebugRepresentationPredicate {
- boolean useDexPcEncoding(DexProgramClass holder, DexEncodedMethod method);
+ int getDexPcEncodingCutoff(DexProgramClass holder, DexEncodedMethod method);
}
public static DebugRepresentationPredicate none(InternalOptions options) {
assert !options.canUseDexPc2PcAsDebugInformation();
- return (holder, method) -> false;
+ return (holder, method) -> NO_PC_ENCODING;
}
public static DebugRepresentationPredicate fromFiles(
@@ -44,8 +47,8 @@
if (!options.canUseDexPc2PcAsDebugInformation()) {
return none(options);
}
- if (options.canUseNativeDexPcInsteadOfDebugInfo() || options.testing.forcePcBasedEncoding) {
- return (holder, method) -> true;
+ if (options.canUseNativeDexPcInsteadOfDebugInfo()) {
+ return (holder, method) -> ALWAYS_PC_ENCODING;
}
// TODO(b/220999985): Avoid the need to maintain a class-to-file map.
Map<DexProgramClass, VirtualFile> classMapping = new IdentityHashMap<>();
@@ -53,12 +56,15 @@
file.classes().forEach(c -> classMapping.put(c, file));
}
return (holder, method) -> {
- if (!isPcCandidate(method)) {
- return false;
+ if (!isPcCandidate(method, options)) {
+ return NO_PC_ENCODING;
}
VirtualFile file = classMapping.get(holder);
DebugRepresentation cutoffs = file.getDebugRepresentation();
- return cutoffs.usesPcEncoding(method);
+ int maxPc = cutoffs.getDexPcEncodingCutoff(method);
+ assert maxPc == NO_PC_ENCODING
+ || verifyLastExecutableInstructionWithinBound(method.getCode().asDexCode(), maxPc);
+ return maxPc;
};
}
@@ -71,8 +77,7 @@
public static void computeForFile(AppView<?> appView, VirtualFile file) {
InternalOptions options = appView.options();
if (!options.canUseDexPc2PcAsDebugInformation()
- || options.canUseNativeDexPcInsteadOfDebugInfo()
- || options.testing.forcePcBasedEncoding) {
+ || options.canUseNativeDexPcInsteadOfDebugInfo()) {
return;
}
// First collect all of the per-pc costs
@@ -88,11 +93,19 @@
}
ProgramMethod method = methods.get(0);
DexEncodedMethod definition = method.getDefinition();
- if (!isPcCandidate(definition)) {
+ if (!isPcCandidate(definition, options)) {
continue;
}
DexCode code = definition.getCode().asDexCode();
DexDebugInfo debugInfo = code.getDebugInfo();
+ if (debugInfo == null) {
+ // If debug info is "null" then the cost of representing it as normal events will be a
+ // single default event to ensure its source file content is active.
+ debugInfo =
+ LineNumberOptimizer.createEventBasedInfoForMethodWithoutDebugInfo(
+ definition, options.dexItemFactory());
+ }
+ assert debugInfo.getParameterCount() == method.getParameters().size();
DexInstruction lastInstruction = getLastExecutableInstruction(code);
if (lastInstruction == null) {
continue;
@@ -105,26 +118,28 @@
}
}
// Second compute the cost of converting to a pc encoding.
- paramCountToCosts.forEach((ignored, summary) -> summary.computeConversionCosts());
+ paramCountToCosts.forEach((ignored, summary) -> summary.computeConversionCosts(appView));
// The result is stored on the virtual files for thread safety.
// TODO(b/220999985): Consider just passing this to the line number optimizer once fixed.
file.setDebugRepresentation(new DebugRepresentation(paramCountToCosts));
}
- private boolean usesPcEncoding(DexEncodedMethod method) {
+ private int getDexPcEncodingCutoff(DexEncodedMethod method) {
DexCode code = method.getCode().asDexCode();
- DexDebugInfo debugInfo = code.getDebugInfo();
- int paramCount = debugInfo.getParameterCount();
+ int paramCount = method.getParameters().size();
+ assert code.getDebugInfo() == null || code.getDebugInfo().getParameterCount() == paramCount;
CostSummary conversionInfo = paramToInfo.get(paramCount);
- if (conversionInfo.cutoff < 0) {
- return false;
+ if (conversionInfo == null || conversionInfo.cutoff < 0) {
+ // We expect all methods calling this to have computed conversion info.
+ assert conversionInfo != null;
+ return NO_PC_ENCODING;
}
DexInstruction lastInstruction = getLastExecutableInstruction(code);
if (lastInstruction == null) {
- return false;
+ return NO_PC_ENCODING;
}
int maxPc = lastInstruction.getOffset();
- return maxPc <= conversionInfo.cutoff;
+ return maxPc <= conversionInfo.cutoff ? conversionInfo.cutoff : NO_PC_ENCODING;
}
@Override
@@ -134,12 +149,12 @@
return StringUtils.join("\n", sorted, CostSummary::toString);
}
- private static boolean isPcCandidate(DexEncodedMethod method) {
+ private static boolean isPcCandidate(DexEncodedMethod method, InternalOptions options) {
if (!method.hasCode() || !method.getCode().isDexCode()) {
return false;
}
DexCode code = method.getCode().asDexCode();
- return LineNumberOptimizer.doesContainPositions(code);
+ return LineNumberOptimizer.mustHaveResidualDebugInfo(code, options);
}
/** The cost of representing normal debug info for all methods with this max pc value. */
@@ -188,7 +203,8 @@
maxPc = Math.max(maxPc, pc);
}
- private void computeConversionCosts() {
+ private void computeConversionCosts(AppView<?> appView) {
+ boolean forcePcBasedEncoding = appView.options().testing.forcePcBasedEncoding;
assert !pcToCost.isEmpty();
// Point at which it is estimated that conversion to PC-encoding is viable.
int currentConvertedPc = -1;
@@ -211,7 +227,7 @@
// If the estimated cost is larger we convert. The order here could be either way as
// both the normal cost and converted cost are estimates. Canonicalization could reduce
// the former and compaction could reduce the latter.
- if (normalOutstandingCost > costToConvert) {
+ if (forcePcBasedEncoding || normalOutstandingCost > costToConvert) {
normalConvertedCost += normalOutstandingCost;
normalOutstandingCost = 0;
currentConvertedPc = currentPc;
@@ -263,7 +279,14 @@
}
}
- private static DexInstruction getLastExecutableInstruction(DexCode code) {
+ public static boolean verifyLastExecutableInstructionWithinBound(DexCode code, int maxPc) {
+ DexInstruction lastExecutableInstruction = getLastExecutableInstruction(code);
+ int offset = lastExecutableInstruction.getOffset();
+ assert offset <= maxPc;
+ return true;
+ }
+
+ public static DexInstruction getLastExecutableInstruction(DexCode code) {
DexInstruction lastInstruction = null;
for (DexInstruction instruction : code.instructions) {
if (!instruction.isPayload()) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 95358b2..d4f1195 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -739,6 +739,12 @@
clazz.getNestHostClassAttribute(), options.itemFactory));
}
+ if (clazz.hasPermittedSubclassAttributes() && options.canUseSealedClasses()) {
+ annotations.add(
+ DexAnnotation.createPermittedSubclassesAnnotation(
+ clazz.getPermittedSubclassAttributes(), options.itemFactory));
+ }
+
if (!annotations.isEmpty()) {
// Append the annotations to annotations array of the class.
DexAnnotation[] copy =
@@ -753,6 +759,7 @@
clazz.clearEnclosingMethodAttribute();
clazz.clearInnerClasses();
clazz.clearClassSignature();
+ clazz.clearPermittedSubclasses();
}
private void insertAttributeAnnotationsForField(DexEncodedField field) {
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 10132f3..50a91d9 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -65,10 +65,12 @@
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.google.common.io.ByteStreams;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
@@ -859,6 +861,7 @@
source,
attrs.nestHostAttribute,
attrs.nestMembersAttribute,
+ attrs.permittedSubclassesAttribute,
attrs.getEnclosingMethodAttribute(),
attrs.getInnerClasses(),
attrs.classSignature,
@@ -1427,6 +1430,7 @@
private ClassSignature classSignature = ClassSignature.noSignature();
private NestHostClassAttribute nestHostAttribute;
private List<NestMemberClassAttribute> nestMembersAttribute = Collections.emptyList();
+ private List<PermittedSubclassAttribute> permittedSubclassesAttribute = Collections.emptyList();
public DexAnnotationSet getAnnotations() {
if (lazyAnnotations != null) {
@@ -1504,6 +1508,14 @@
nestMembersAttribute.add(new NestMemberClassAttribute(member));
}
}
+ } else if (DexAnnotation.isPermittedSubclassesAnnotation(annotation, factory)) {
+ ensureAnnotations(i);
+ List<DexType> permittedSubclasses =
+ DexAnnotation.getPermittedSubclassesFromAnnotation(annotation, factory);
+ if (permittedSubclasses != null) {
+ permittedSubclassesAttribute =
+ ListUtils.map(permittedSubclasses, PermittedSubclassAttribute::new);
+ }
} else {
copyAnnotation(annotation);
}
diff --git a/src/main/java/com/android/tools/r8/errors/AssumeValuesMissingStaticFieldDiagnostic.java b/src/main/java/com/android/tools/r8/errors/AssumeValuesMissingStaticFieldDiagnostic.java
new file mode 100644
index 0000000..38f6a46
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/AssumeValuesMissingStaticFieldDiagnostic.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2022, 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+@Keep
+public class AssumeValuesMissingStaticFieldDiagnostic implements Diagnostic {
+
+ private final DexType fieldHolder;
+ private final DexString fieldName;
+ private final Origin origin;
+ private final Position position;
+
+ private AssumeValuesMissingStaticFieldDiagnostic(
+ DexType fieldHolder, DexString fieldName, Origin origin, Position position) {
+ this.fieldHolder = fieldHolder;
+ this.fieldName = fieldName;
+ this.origin = origin;
+ this.position = position;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+
+ @Override
+ public Position getPosition() {
+ return position;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ return "The field "
+ + fieldHolder.getTypeName()
+ + "."
+ + fieldName
+ + " is used as the return value in an -assumenosideeffects or -assumevalues rule"
+ + ", but no such static field exists.";
+ }
+
+ public static class Builder {
+
+ private DexType fieldHolder;
+ private DexString fieldName;
+ private Origin origin;
+ private Position position;
+
+ public Builder() {}
+
+ public Builder setField(DexType fieldHolder, DexString fieldName) {
+ this.fieldHolder = fieldHolder;
+ this.fieldName = fieldName;
+ return this;
+ }
+
+ public Builder setOrigin(Origin origin) {
+ this.origin = origin;
+ return this;
+ }
+
+ public Builder setPosition(Position position) {
+ this.position = position;
+ return this;
+ }
+
+ public AssumeValuesMissingStaticFieldDiagnostic build() {
+ return new AssumeValuesMissingStaticFieldDiagnostic(fieldHolder, fieldName, origin, position);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/CheckEnumUnboxedDiagnostic.java b/src/main/java/com/android/tools/r8/errors/CheckEnumUnboxedDiagnostic.java
new file mode 100644
index 0000000..3339192
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/CheckEnumUnboxedDiagnostic.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.google.common.collect.ImmutableList;
+import java.util.Comparator;
+import java.util.List;
+
+@Keep
+public class CheckEnumUnboxedDiagnostic implements Diagnostic {
+
+ private final List<String> messages;
+
+ CheckEnumUnboxedDiagnostic(List<String> messages) {
+ this.messages = messages;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /** The origin of a -checkenumunboxed failure is not unique. (The whole app is to blame.) */
+ @Override
+ public Origin getOrigin() {
+ return Origin.unknown();
+ }
+
+ /** The position of a -checkenumunboxed failure is always unknown. */
+ @Override
+ public Position getPosition() {
+ return Position.UNKNOWN;
+ }
+
+ @Override
+ public String getDiagnosticMessage() {
+ StringBuilder builder = new StringBuilder("Enum unboxing checks failed.");
+ for (String message : messages) {
+ builder.append(System.lineSeparator());
+ builder.append(message);
+ }
+ return builder.toString();
+ }
+
+ public static class Builder {
+
+ private final ImmutableList.Builder<String> messagesBuilder = ImmutableList.builder();
+
+ public Builder addFailedEnums(List<DexProgramClass> failed) {
+ failed.sort(Comparator.comparing(DexClass::getType));
+ for (DexProgramClass clazz : failed) {
+ messagesBuilder.add("Enum " + clazz.getTypeName() + " was not unboxed.");
+ }
+ return this;
+ }
+
+ public CheckEnumUnboxedDiagnostic build() {
+ return new CheckEnumUnboxedDiagnostic(messagesBuilder.build());
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
index 7fad730..c633722 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOptions.java
@@ -4,20 +4,21 @@
package com.android.tools.r8.experimental.startup;
-import static com.android.tools.r8.utils.InternalOptions.getSystemPropertyForDevelopmentOrDefault;
-import static com.android.tools.r8.utils.InternalOptions.isSystemPropertyForDevelopmentSet;
+import static com.android.tools.r8.utils.SystemPropertyUtils.getSystemPropertyForDevelopment;
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
public class StartupOptions {
private boolean enableMinimalStartupDex =
- isSystemPropertyForDevelopmentSet("com.android.tools.r8.startup.minimalstartupdex");
+ parseSystemPropertyForDevelopmentOrDefault(
+ "com.android.tools.r8.startup.minimalstartupdex", false);
private boolean enableStartupCompletenessCheckForTesting =
- isSystemPropertyForDevelopmentSet("com.android.tools.r8.startup.completenesscheck");
+ parseSystemPropertyForDevelopmentOrDefault(
+ "com.android.tools.r8.startup.completenesscheck", false);
private boolean enableStartupInstrumentation =
- isSystemPropertyForDevelopmentSet("com.android.tools.r8.startup.instrument");
+ parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.startup.instrument", false);
private String startupInstrumentationTag =
- getSystemPropertyForDevelopmentOrDefault(
- "com.android.tools.r8.startup.instrumentationtag", null);
+ getSystemPropertyForDevelopment("com.android.tools.r8.startup.instrumentationtag");
private StartupConfiguration startupConfiguration;
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 6dd2ee7..b4926fe 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -34,7 +34,9 @@
import com.android.tools.r8.naming.SeedMapper;
import com.android.tools.r8.optimize.argumentpropagation.ArgumentPropagator;
import com.android.tools.r8.optimize.interfaces.collection.OpenClosedInterfacesCollection;
+import com.android.tools.r8.retrace.internal.RetraceUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
import com.android.tools.r8.shaking.KeepClassInfo;
import com.android.tools.r8.shaking.KeepFieldInfo;
import com.android.tools.r8.shaking.KeepInfoCollection;
@@ -74,6 +76,7 @@
private T appInfo;
private AppInfoWithClassHierarchy appInfoForDesugaring;
private AppServices appServices;
+ private AssumeInfoCollection assumeInfoCollection = AssumeInfoCollection.builder().build();
private final DontWarnConfiguration dontWarnConfiguration;
private final WholeProgramOptimizations wholeProgramOptimizations;
private GraphLens codeLens = GraphLens.getIdentityLens();
@@ -115,7 +118,8 @@
OpenClosedInterfacesCollection.getDefault();
// TODO(b/169115389): Remove
private Set<DexMethod> cfByteCodePassThrough = ImmutableSet.of();
- private Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
+ private final Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
+ private final Map<DexType, String> sourceFileForPrunedTypes = new IdentityHashMap<>();
private SeedMapper applyMappingSeedMapper;
@@ -311,6 +315,14 @@
this.appServices = appServices;
}
+ public AssumeInfoCollection getAssumeInfoCollection() {
+ return assumeInfoCollection;
+ }
+
+ public void setAssumeInfoCollection(AssumeInfoCollection assumeInfoCollection) {
+ this.assumeInfoCollection = assumeInfoCollection;
+ }
+
public DontWarnConfiguration getDontWarnConfiguration() {
return dontWarnConfiguration;
}
@@ -760,6 +772,7 @@
if (appServices() != null) {
setAppServices(appServices().prunedCopy(prunedItems));
}
+ setAssumeInfoCollection(getAssumeInfoCollection().withoutPrunedItems(prunedItems));
if (hasProguardCompatibilityActions()) {
setProguardCompatibilityActions(
getProguardCompatibilityActions().withoutPrunedItems(prunedItems));
@@ -857,6 +870,8 @@
.setAppInfo(appView.appInfoWithLiveness().rewrittenWithLens(application, lens));
}
appView.setAppServices(appView.appServices().rewrittenWithLens(lens));
+ appView.setAssumeInfoCollection(
+ appView.getAssumeInfoCollection().rewrittenWithLens(appView, lens));
if (appView.hasInitClassLens()) {
appView.setInitClassLens(appView.initClassLens().rewrittenWithLens(lens));
}
@@ -915,4 +930,14 @@
public ComputedApiLevel computedMinApiLevel() {
return computedMinApiLevel;
}
+
+ public void addPrunedClassSourceFile(DexType prunedType, String sourceFile) {
+ if (!RetraceUtils.hasPredictableSourceFileName(prunedType.toSourceString(), sourceFile)) {
+ sourceFileForPrunedTypes.put(prunedType, sourceFile);
+ }
+ }
+
+ public String getPrunedClassSourceFileInfo(DexType dexType) {
+ return sourceFileForPrunedTypes.get(dexType);
+ }
}
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 42d0bcd..f0598cd 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -953,7 +953,7 @@
CfCodeStackMapValidatingException.noFramesForMethodWithJumps(method, appView), appView);
}
CfFrameVerificationHelper helper =
- new CfFrameVerificationHelper(appView, this, method, stateMap, tryCatchRanges);
+ new CfFrameVerificationHelper(appView, this, codeLens, method, stateMap, tryCatchRanges);
CfCodeDiagnostics diagnostics = helper.checkTryCatchRanges();
if (diagnostics != null) {
return reportStackMapError(diagnostics, appView);
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 60f78ea..e878e0d 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -26,6 +26,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
@@ -47,6 +48,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
@@ -69,6 +71,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
@@ -90,6 +93,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
@@ -110,6 +114,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
@@ -131,6 +136,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
@@ -152,6 +158,7 @@
DexString sourceFile,
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
+ List<PermittedSubclassAttribute> permittedSubclasses,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -183,6 +190,7 @@
DexString sourceFile,
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
+ List<PermittedSubclassAttribute> permittedSubclasses,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -204,6 +212,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index c43d396..924590f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -186,6 +186,11 @@
return annotation.annotation.type == factory.annotationNestMembers;
}
+ public static boolean isPermittedSubclassesAnnotation(
+ DexAnnotation annotation, DexItemFactory factory) {
+ return annotation.annotation.type == factory.annotationPermittedSubclasses;
+ }
+
public static DexAnnotation createInnerClassAnnotation(
DexString clazz, int access, DexItemFactory factory) {
return new DexAnnotation(
@@ -252,9 +257,9 @@
return value.asDexValueType().getValue();
}
- public static List<DexType> getNestMembersFromAnnotation(
- DexAnnotation annotation, DexItemFactory factory) {
- DexValue value = getSystemValueAnnotationValue(factory.annotationNestMembers, annotation);
+ private static List<DexType> getTypesFromAnnotation(
+ DexType annotationType, DexAnnotation annotation) {
+ DexValue value = getSystemValueAnnotationValue(annotationType, annotation);
if (value == null) {
return null;
}
@@ -266,6 +271,16 @@
return types;
}
+ public static List<DexType> getNestMembersFromAnnotation(
+ DexAnnotation annotation, DexItemFactory factory) {
+ return getTypesFromAnnotation(factory.annotationNestMembers, annotation);
+ }
+
+ public static List<DexType> getPermittedSubclassesFromAnnotation(
+ DexAnnotation annotation, DexItemFactory factory) {
+ return getTypesFromAnnotation(factory.annotationPermittedSubclasses, annotation);
+ }
+
public static DexAnnotation createSourceDebugExtensionAnnotation(DexValue value,
DexItemFactory factory) {
return new DexAnnotation(VISIBILITY_SYSTEM,
@@ -322,6 +337,18 @@
new DexValue.DexValueArray(list.toArray(DexValue.EMPTY_ARRAY)));
}
+ public static DexAnnotation createPermittedSubclassesAnnotation(
+ List<PermittedSubclassAttribute> permittedSubclasses, DexItemFactory factory) {
+ List<DexValueType> list = new ArrayList<>(permittedSubclasses.size());
+ for (PermittedSubclassAttribute permittedSubclass : permittedSubclasses) {
+ list.add(new DexValue.DexValueType(permittedSubclass.getPermittedSubclass()));
+ }
+ return createSystemValueAnnotation(
+ factory.annotationPermittedSubclasses,
+ factory,
+ new DexValue.DexValueArray(list.toArray(DexValue.EMPTY_ARRAY)));
+ }
+
public static String getSignature(DexAnnotation signatureAnnotation) {
DexValueArray elements = signatureAnnotation.annotation.elements[0].value.asDexValueArray();
StringBuilder signature = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index a377b46..f10a654 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -80,6 +80,8 @@
private List<NestMemberClassAttribute> nestMembers;
+ private List<PermittedSubclassAttribute> permittedSubclasses;
+
/** Generic signature information if the attribute is present in the input */
protected ClassSignature classSignature;
@@ -94,6 +96,7 @@
MethodCollectionFactory methodCollectionFactory,
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
+ List<PermittedSubclassAttribute> permittedSubclasses,
EnclosingMethodAttribute enclosingMethod,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -114,6 +117,8 @@
this.nestHost = nestHost;
this.nestMembers = nestMembers;
assert nestMembers != null;
+ this.permittedSubclasses = permittedSubclasses;
+ assert permittedSubclasses != null;
this.enclosingMethod = enclosingMethod;
this.innerClasses = innerClasses;
assert classSignature != null;
@@ -539,17 +544,26 @@
return lookupTarget(instanceFields, field);
}
- public DexField lookupUniqueInstanceFieldWithName(DexString name) {
- DexField field = null;
- for (DexEncodedField encodedField : instanceFields()) {
- if (encodedField.getReference().name == name) {
- if (field != null) {
+ public DexEncodedField lookupUniqueInstanceFieldWithName(DexString name) {
+ return internalLookupUniqueFieldThatMatches(field -> field.getName() == name, instanceFields());
+ }
+
+ public DexEncodedField lookupUniqueStaticFieldWithName(DexString name) {
+ return internalLookupUniqueFieldThatMatches(field -> field.getName() == name, staticFields());
+ }
+
+ private static DexEncodedField internalLookupUniqueFieldThatMatches(
+ Predicate<DexEncodedField> predicate, List<DexEncodedField> fields) {
+ DexEncodedField result = null;
+ for (DexEncodedField field : fields) {
+ if (predicate.test(field)) {
+ if (result != null) {
return null;
}
- field = encodedField.getReference();
+ result = field;
}
}
- return field;
+ return result;
}
/** Find method in this class matching {@param method}. */
@@ -1102,6 +1116,10 @@
this.classSignature = classSignature;
}
+ public void clearPermittedSubclasses() {
+ permittedSubclasses.clear();
+ }
+
public boolean isLocalClass() {
InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
// The corresponding enclosing-method attribute might be not available, e.g., CF version 50.
@@ -1171,7 +1189,7 @@
}
public boolean hasNestMemberAttributes() {
- return nestMembers != null && !nestMembers.isEmpty();
+ return !nestMembers.isEmpty();
}
public List<NestMemberClassAttribute> getNestMembersClassAttributes() {
@@ -1186,6 +1204,14 @@
nestMembers.removeIf(predicate);
}
+ public boolean hasPermittedSubclassAttributes() {
+ return !permittedSubclasses.isEmpty();
+ }
+
+ public List<PermittedSubclassAttribute> getPermittedSubclassAttributes() {
+ return permittedSubclasses;
+ }
+
/** Returns kotlin class info if the class is synthesized by kotlin compiler. */
public abstract KotlinClassLevelInfo getKotlinInfo();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index 5130c57..43c2532 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -35,6 +35,7 @@
DexString sourceFile,
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
+ List<PermittedSubclassAttribute> permittedSubclasses,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -54,6 +55,7 @@
methodCollectionFactory,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index b6f5fd9..8469b4e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.DebugBytecodeWriter;
import com.android.tools.r8.dex.IndexedItemCollection;
@@ -97,6 +98,10 @@
this.maxPc = maxPc;
}
+ public int getMaxPc() {
+ return maxPc;
+ }
+
@Override
public int getStartLine() {
return START_LINE;
@@ -294,19 +299,18 @@
}
assert code.getDebugInfo().isPcBasedInfo();
PcBasedDebugInfo pcBasedDebugInfo = code.getDebugInfo().asPcBasedInfo();
+ assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
+ code, pcBasedDebugInfo.maxPc);
// Generate a line event at each throwing instruction.
List<DexDebugEvent> events = new ArrayList<>(code.instructions.length);
- int pc = 0;
int delta = 0;
for (DexInstruction instruction : code.instructions) {
if (instruction.canThrow()) {
DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary(delta, delta, events, factory);
- pc += delta;
delta = 0;
}
delta += instruction.getSize();
}
- assert pc + delta - ArrayUtils.last(code.instructions).getSize() <= pcBasedDebugInfo.maxPc;
return new EventBasedDebugInfo(
PcBasedDebugInfo.START_LINE,
new DexString[pcBasedDebugInfo.getParameterCount()],
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 36f8d0f..e9f0446 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -646,6 +646,8 @@
createStaticallyKnownType("Ldalvik/annotation/NestHost;");
public final DexType annotationNestMembers =
createStaticallyKnownType("Ldalvik/annotation/NestMembers;");
+ public final DexType annotationPermittedSubclasses =
+ createStaticallyKnownType("Ldalvik/annotation/PermittedSubclasses;");
public final DexType annotationSourceDebugExtension =
createStaticallyKnownType("Ldalvik/annotation/SourceDebugExtension;");
public final DexType annotationThrows = createStaticallyKnownType("Ldalvik/annotation/Throws;");
@@ -750,9 +752,7 @@
public BoxedPrimitiveMembers getBoxedMembersForPrimitiveOrVoidType(DexType type) {
assert type.isPrimitiveType() || type.isVoidType();
- char c = (char) type.getDescriptor().content[0];
- assert c == type.toDescriptorString().charAt(0);
- switch (c) {
+ switch (type.getDescriptor().getFirstByteAsChar()) {
case 'B':
return byteMembers;
case 'C':
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index e5cd61c..24091e4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -33,6 +33,7 @@
DexString sourceFile,
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
+ List<PermittedSubclassAttribute> permittedSubclasses,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -52,6 +53,7 @@
methodCollectionFactory,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
@@ -171,6 +173,7 @@
private DexString sourceFile = null;
private NestHostClassAttribute nestHost = null;
private List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
+ private List<PermittedSubclassAttribute> permittedSubclasses = Collections.emptyList();
private EnclosingMethodAttribute enclosingMember = null;
private List<InnerClassAttribute> innerClasses = Collections.emptyList();
private ClassSignature classSignature = ClassSignature.noSignature();
@@ -213,6 +216,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 46bebdc..b49abbe 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -69,6 +69,7 @@
DexString sourceFile,
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
+ List<PermittedSubclassAttribute> permittedSubclasses,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -90,6 +91,7 @@
methodCollectionFactory,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
@@ -113,6 +115,7 @@
DexString sourceFile,
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
+ List<PermittedSubclassAttribute> permittedSubclasses,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -132,6 +135,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 87630f2..f6d0d19 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -35,6 +35,10 @@
this.content = encodeToMutf8(string);
}
+ public char getFirstByteAsChar() {
+ return (char) content[0];
+ }
+
@Override
public DexString self() {
return this;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index df11e7e..1efc762 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -223,7 +223,7 @@
}
public char toShorty() {
- char c = (char) descriptor.content[0];
+ char c = descriptor.getFirstByteAsChar();
return c == '[' ? 'L' : c;
}
@@ -266,58 +266,58 @@
}
public boolean isPrimitiveType() {
- return DescriptorUtils.isPrimitiveType((char) descriptor.content[0]);
+ return DescriptorUtils.isPrimitiveType(descriptor.getFirstByteAsChar());
}
public boolean isVoidType() {
- return (char) descriptor.content[0] == 'V';
+ return descriptor.getFirstByteAsChar() == 'V';
}
public boolean isBooleanType() {
- return descriptor.content[0] == 'Z';
+ return descriptor.getFirstByteAsChar() == 'Z';
}
public boolean isByteType() {
- return descriptor.content[0] == 'B';
+ return descriptor.getFirstByteAsChar() == 'B';
}
public boolean isCharType() {
- return descriptor.content[0] == 'C';
+ return descriptor.getFirstByteAsChar() == 'C';
}
public boolean isShortType() {
- return descriptor.content[0] == 'S';
+ return descriptor.getFirstByteAsChar() == 'S';
}
public boolean isIntType() {
- return descriptor.content[0] == 'I';
+ return descriptor.getFirstByteAsChar() == 'I';
}
public boolean isFloatType() {
- return descriptor.content[0] == 'F';
+ return descriptor.getFirstByteAsChar() == 'F';
}
public boolean isLongType() {
- return descriptor.content[0] == 'J';
+ return descriptor.getFirstByteAsChar() == 'J';
}
public boolean isDoubleType() {
- return descriptor.content[0] == 'D';
+ return descriptor.getFirstByteAsChar() == 'D';
}
public boolean isNullValueType() {
- boolean isNullValueType = descriptor.content[0] == 'N';
+ boolean isNullValueType = descriptor.getFirstByteAsChar() == 'N';
assert !isNullValueType || this == DexItemFactory.nullValueType;
return isNullValueType;
}
public boolean isArrayType() {
- char firstChar = (char) descriptor.content[0];
+ char firstChar = descriptor.getFirstByteAsChar();
return firstChar == '[';
}
public boolean isClassType() {
- char firstChar = (char) descriptor.content[0];
+ char firstChar = descriptor.getFirstByteAsChar();
return firstChar == 'L';
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index d88983a..0237c6d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -226,6 +226,7 @@
private DexString sourceFile;
private NestHostClassAttribute nestHost = null;
private final List<NestMemberClassAttribute> nestMembers = new ArrayList<>();
+ private final List<PermittedSubclassAttribute> permittedSubclasses = new ArrayList<>();
private final Set<DexField> recordComponents = Sets.newIdentityHashSet();
private EnclosingMethodAttribute enclosingMember = null;
private final List<InnerClassAttribute> innerClasses = new ArrayList<>();
@@ -348,11 +349,9 @@
@Override
public void visitPermittedSubclass(String permittedSubclass) {
- if (classKind == ClassKind.PROGRAM) {
- throw new CompilationError("Sealed classes are not supported as program classes", origin);
- }
- // For library and classpath just ignore the permitted subclasses, as the compiler is not
- // validating the code with respect to sealed classes.
+ assert permittedSubclass != null;
+ DexType permittedSubclassType = application.getTypeFromName(permittedSubclass);
+ permittedSubclasses.add(new PermittedSubclassAttribute(permittedSubclassType));
}
@Override
@@ -476,6 +475,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMember,
innerClasses,
classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/PermittedSubclassAttribute.java b/src/main/java/com/android/tools/r8/graph/PermittedSubclassAttribute.java
new file mode 100644
index 0000000..0c4dd43
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/PermittedSubclassAttribute.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.util.Collections;
+import java.util.List;
+import org.objectweb.asm.ClassWriter;
+
+public class PermittedSubclassAttribute implements StructuralItem<PermittedSubclassAttribute> {
+
+ private final DexType permittedSubclass;
+
+ private static void specify(StructuralSpecification<PermittedSubclassAttribute, ?> spec) {
+ spec.withItem(a -> a.permittedSubclass);
+ }
+
+ public PermittedSubclassAttribute(DexType nestMember) {
+ this.permittedSubclass = nestMember;
+ }
+
+ public static List<PermittedSubclassAttribute> emptyList() {
+ return Collections.emptyList();
+ }
+
+ public DexType getPermittedSubclass() {
+ return permittedSubclass;
+ }
+
+ public void write(ClassWriter writer, NamingLens lens) {
+ assert permittedSubclass != null;
+ writer.visitPermittedSubclass(lens.lookupInternalName(permittedSubclass));
+ }
+
+ @Override
+ public PermittedSubclassAttribute self() {
+ return this;
+ }
+
+ @Override
+ public StructuralMapping<PermittedSubclassAttribute> getStructuralMapping() {
+ return PermittedSubclassAttribute::specify;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 0828ff2..1c99b6c 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -107,6 +107,7 @@
clazz.getSourceFile(),
fixupNestHost(clazz.getNestHostClassAttribute()),
fixupNestMemberAttributes(clazz.getNestMembersClassAttributes()),
+ fixupPermittedSubclassAttribute(clazz.getPermittedSubclassAttributes()),
fixupEnclosingMethodAttribute(clazz.getEnclosingMethodAttribute()),
fixupInnerClassAttributes(clazz.getInnerClasses()),
clazz.getClassSignature(),
@@ -271,6 +272,23 @@
return changed ? newNestMemberAttributes : nestMemberAttributes;
}
+ protected List<PermittedSubclassAttribute> fixupPermittedSubclassAttribute(
+ List<PermittedSubclassAttribute> permittedSubclassAttributes) {
+ if (permittedSubclassAttributes.isEmpty()) {
+ return permittedSubclassAttributes;
+ }
+ boolean changed = false;
+ List<PermittedSubclassAttribute> newPermittedSubclassAttributes =
+ new ArrayList<>(permittedSubclassAttributes.size());
+ for (PermittedSubclassAttribute permittedSubclassAttribute : permittedSubclassAttributes) {
+ DexType permittedSubclassType = permittedSubclassAttribute.getPermittedSubclass();
+ DexType newPermittedSubclassType = fixupType(permittedSubclassType);
+ newPermittedSubclassAttributes.add(new PermittedSubclassAttribute(newPermittedSubclassType));
+ changed |= newPermittedSubclassType != permittedSubclassType;
+ }
+ return changed ? newPermittedSubclassAttributes : permittedSubclassAttributes;
+ }
+
/** Fixup a proto descriptor. */
public DexProto fixupProto(DexProto proto) {
DexProto result = protoFixupCache.get(proto);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
index ab305a3..a6e27f5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreserveMethodCharacteristics.java
@@ -60,7 +60,7 @@
AppView<AppInfoWithLiveness> appView, DexEncodedMethod method) {
return new MethodCharacteristics(
method,
- appView.appInfo().isAssumeNoSideEffectsMethod(method.getReference()),
+ appView.getAssumeInfoCollection().isSideEffectFree(method.getReference()),
appView.appInfo().getMainDexInfo().isTracedMethodRoot(method.getReference()));
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index eb22b19..fbe3ff5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -202,10 +202,10 @@
return new ProtoTypeObject(constClass.getValue());
} else if (definition.isConstString()) {
ConstString constString = definition.asConstString();
- DexField field =
+ DexEncodedField field =
context.getHolder().lookupUniqueInstanceFieldWithName(constString.getValue());
if (field != null) {
- return new LiveProtoFieldObject(field);
+ return new LiveProtoFieldObject(field.getReference());
}
// This const-string refers to a field that no longer exists. In this case, we create a
// special dead-object instead of failing with an InvalidRawMessageInfoException below.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
index bc1a554..bab5e75 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/AbstractValueFactory.java
@@ -31,6 +31,11 @@
return knownArrayLengthStates.computeIfAbsent(length, KnownLengthArrayState::new);
}
+ public NumberFromIntervalValue createNumberFromIntervalValue(
+ long minInclusive, long maxInclusive) {
+ return new NumberFromIntervalValue(minInclusive, maxInclusive);
+ }
+
public SingleFieldValue createSingleFieldValue(DexField field, ObjectState state) {
return state.isEmpty()
? new SingleStatelessFieldValue(field)
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
index 3238680..94d2d59 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/NumberFromIntervalValue.java
@@ -31,6 +31,14 @@
return maxInclusive - minInclusive + 1;
}
+ public long getMinInclusive() {
+ return minInclusive;
+ }
+
+ public long getMaxInclusive() {
+ return maxInclusive;
+ }
+
@Override
public boolean isNumberFromIntervalValue() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index d090011..fe41e44 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -67,7 +67,7 @@
@Override
public Instruction createMaterializingInstruction(
- AppView<? extends AppInfoWithClassHierarchy> appView,
+ AppView<?> appView,
ProgramMethod context,
NumberGenerator valueNumberGenerator,
TypeAndLocalInfoSupplier info) {
@@ -88,8 +88,8 @@
}
@Override
- public boolean isMaterializableInContext(
- AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+ boolean internalIsMaterializableInContext(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
DexType baseType = type.toBaseType(appView.dexItemFactory());
if (baseType.isClassType()) {
DexClass clazz = appView.definitionFor(baseType);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
index 8a69170..c1e6c43 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleDexItemBasedStringValue.java
@@ -75,7 +75,7 @@
@Override
public Instruction createMaterializingInstruction(
- AppView<? extends AppInfoWithClassHierarchy> appView,
+ AppView<?> appView,
ProgramMethod context,
NumberGenerator valueNumberGenerator,
TypeAndLocalInfoSupplier info) {
@@ -97,8 +97,8 @@
}
@Override
- public boolean isMaterializableInContext(
- AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+ boolean internalIsMaterializableInContext(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 1c50dd7..d7442d5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -85,19 +85,21 @@
@Override
public Instruction createMaterializingInstruction(
- AppView<? extends AppInfoWithClassHierarchy> appView,
+ AppView<?> appView,
ProgramMethod context,
NumberGenerator valueNumberGenerator,
TypeAndLocalInfoSupplier info) {
TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
- assert type.lessThanOrEqual(info.getOutType(), appView) || type.isBasedOnMissingClass(appView);
+ assert type.lessThanOrEqual(info.getOutType(), appView)
+ || !appView.enableWholeProgramOptimizations()
+ || type.isBasedOnMissingClass(appView.withClassHierarchy());
Value outValue = new Value(valueNumberGenerator.next(), type, info.getLocalInfo());
return new StaticGet(outValue, field);
}
@Override
- public boolean isMaterializableInContext(
- AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+ boolean internalIsMaterializableInContext(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
return allMatch(
appView.appInfo().resolveField(field)::forEachFieldResolutionResult,
resolutionResult -> {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index c9b7f62..934f0e6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -127,7 +127,7 @@
@Override
public Instruction createMaterializingInstruction(
- AppView<? extends AppInfoWithClassHierarchy> appView,
+ AppView<?> appView,
ProgramMethod context,
NumberGenerator valueNumberGenerator,
TypeAndLocalInfoSupplier info) {
@@ -143,8 +143,8 @@
}
@Override
- public boolean isMaterializableInContext(
- AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+ boolean internalIsMaterializableInContext(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index 6e69356..f4ce6ae 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -63,7 +63,7 @@
@Override
public Instruction createMaterializingInstruction(
- AppView<? extends AppInfoWithClassHierarchy> appView,
+ AppView<?> appView,
ProgramMethod context,
NumberGenerator valueNumberGenerator,
TypeAndLocalInfoSupplier info) {
@@ -84,8 +84,8 @@
}
@Override
- public boolean isMaterializableInContext(
- AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
+ boolean internalIsMaterializableInContext(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
index 390ac7b..0f0ff90 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleValue.java
@@ -37,20 +37,27 @@
* #isMaterializableInContext}.
*/
public final Instruction createMaterializingInstruction(
- AppView<? extends AppInfoWithClassHierarchy> appView,
- IRCode code,
- TypeAndLocalInfoSupplier info) {
+ AppView<?> appView, IRCode code, TypeAndLocalInfoSupplier info) {
return createMaterializingInstruction(appView, code.context(), code.valueNumberGenerator, info);
}
public abstract Instruction createMaterializingInstruction(
- AppView<? extends AppInfoWithClassHierarchy> appView,
+ AppView<?> appView,
ProgramMethod context,
NumberGenerator valueNumberGenerator,
TypeAndLocalInfoSupplier info);
- public abstract boolean isMaterializableInContext(
- AppView<AppInfoWithLiveness> appView, ProgramMethod context);
+ public final boolean isMaterializableInContext(AppView<?> appView, ProgramMethod context) {
+ if (appView.enableWholeProgramOptimizations()) {
+ assert appView.hasClassHierarchy();
+ return internalIsMaterializableInContext(appView.withClassHierarchy(), context);
+ }
+ // All abstract values created in D8 should be accessible in all contexts.
+ return true;
+ }
+
+ abstract boolean internalIsMaterializableInContext(
+ AppView<? extends AppInfoWithClassHierarchy> appView, ProgramMethod context);
public abstract boolean isMaterializableInAllContexts(AppView<AppInfoWithLiveness> appView);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index b9f07e3..bdbe697 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -22,7 +22,6 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
import com.android.tools.r8.ir.optimize.NestUtils;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import com.google.common.collect.ImmutableList;
@@ -331,10 +330,7 @@
@Override
public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
- AppView<AppInfoWithLiveness> appView,
- IRCode code,
- DexType type,
- Consumer<InitClass> consumer) {
+ AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer) {
Instruction toBeReplaced = current;
assert toBeReplaced != null;
assert toBeReplaced.isStaticFieldInstruction() || toBeReplaced.isInvokeStatic();
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 552ebf3..3551561 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -83,9 +84,9 @@
}
SingleFieldResolutionResult<?> singleFieldResolutionResult =
resolutionResult.asSingleFieldResolutionResult();
- DexEncodedField resolvedField = singleFieldResolutionResult.getResolvedField();
+ DexClassAndField resolvedField = singleFieldResolutionResult.getResolutionPair();
// Check if the instruction may fail with an IncompatibleClassChangeError.
- if (resolvedField.isStatic() != isStaticFieldInstruction()) {
+ if (resolvedField.getAccessFlags().isStatic() != isStaticFieldInstruction()) {
return true;
}
// Check if the resolution target is accessible.
@@ -115,11 +116,8 @@
isStaticFieldInstruction() && !assumption.canAssumeClassIsAlreadyInitialized();
if (mayTriggerClassInitialization) {
// Only check for <clinit> side effects if there is no -assumenosideeffects rule.
- if (appView.appInfo().hasLiveness()) {
- AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
- if (appInfoWithLiveness.noSideEffects.containsKey(resolvedField.getReference())) {
- return false;
- }
+ if (appView.getAssumeInfoCollection().isSideEffectFree(resolvedField)) {
+ return false;
}
// May trigger <clinit> that may have side effects.
if (field.holder.classInitializationMayHaveSideEffectsInContext(appView, context)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
index 9125869..4962981 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionListIterator.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import java.util.ListIterator;
import java.util.NoSuchElementException;
@@ -63,10 +62,7 @@
@Override
public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
- AppView<AppInfoWithLiveness> appView,
- IRCode code,
- DexType type,
- Consumer<InitClass> consumer) {
+ AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer) {
return instructionIterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
appView, code, type, consumer);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 5055072..e13fe25 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
@@ -111,16 +110,13 @@
boolean replaceCurrentInstructionByNullCheckIfPossible(AppView<?> appView, ProgramMethod context);
default boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
- AppView<AppInfoWithLiveness> appView, IRCode code, DexType type) {
+ AppView<?> appView, IRCode code, DexType type) {
return removeOrReplaceCurrentInstructionByInitClassIfPossible(
appView, code, type, ConsumerUtils.emptyConsumer());
}
boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
- AppView<AppInfoWithLiveness> appView,
- IRCode code,
- DexType type,
- Consumer<InitClass> consumer);
+ AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer);
void replaceCurrentInstructionWithConstClass(
AppView<?> appView, IRCode code, DexType type, DebugLocalInfo localInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 668d515..f5e7b03 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -216,12 +216,10 @@
}
DexClassAndMethod resolvedMethod = resolutionResult.getResolutionPair();
- if (appView.hasLiveness()) {
- if (appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(getInvokedMethod())
- || appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(resolvedMethod)) {
+ if (appView.getAssumeInfoCollection().isSideEffectFree(getInvokedMethod())
+ || appView.getAssumeInfoCollection().isSideEffectFree(resolvedMethod)) {
return false;
}
- }
// Find the target and check if the invoke may have side effects.
DexClassAndMethod singleTarget = lookupSingleTarget(appView, context);
@@ -237,10 +235,8 @@
}
// Verify that the target method does not have side-effects.
- if (appView.hasLiveness()) {
- if (appView.appInfoWithLiveness().isAssumeNoSideEffectsMethod(singleTarget)) {
- return false;
- }
+ if (appView.getAssumeInfoCollection().isSideEffectFree(singleTarget)) {
+ return false;
}
DexEncodedMethod singleTargetDefinition = singleTarget.getDefinition();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 8f47b31..9e6a918 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -220,7 +220,7 @@
}
// Verify that the target method does not have side-effects.
- if (appViewWithLiveness.appInfo().isAssumeNoSideEffectsMethod(singleTarget)) {
+ if (appView.getAssumeInfoCollection().isSideEffectFree(singleTarget)) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
index 093faf1..91b51be 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LinearFlowInstructionListIterator.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.ListIterator;
@@ -87,10 +86,7 @@
@Override
public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
- AppView<AppInfoWithLiveness> appView,
- IRCode code,
- DexType type,
- Consumer<InitClass> consumer) {
+ AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer) {
return currentBlockIterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
appView, code, type, consumer);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index a078dde..cdec1d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -154,6 +154,15 @@
}
@Override
+ public boolean instructionInstanceCanThrow(
+ AppView<?> appView, ProgramMethod context, SideEffectAssumption assumption) {
+ if (appView.getAssumeInfoCollection().isSideEffectFree(getField())) {
+ return false;
+ }
+ return super.instructionInstanceCanThrow(appView, context, assumption);
+ }
+
+ @Override
public int maxInValueRegister() {
return Constants.U8BIT_MAX;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index bb83120..54710df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -27,6 +27,7 @@
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.NumberFromIntervalValue;
import com.android.tools.r8.ir.analysis.value.UnknownValue;
import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
import com.android.tools.r8.ir.regalloc.LiveIntervals;
@@ -940,8 +941,8 @@
return isThis;
}
- public void setValueRange(LongInterval range) {
- valueRange = range;
+ public void setValueRange(NumberFromIntervalValue range) {
+ valueRange = new LongInterval(range.getMinInclusive(), range.getMaxInclusive());
}
public boolean hasValueRange() {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 201c789..ec93ef2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -55,6 +55,7 @@
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewArrayFilledData;
import com.android.tools.r8.ir.code.Position;
@@ -351,11 +352,10 @@
&& currentBlock.getPredecessors().get(0) == previousBlock;
}
- private static void removeTrivialFallthroughBlocks(IRCode code) {
- for (int blockIndex = 1; blockIndex < code.blocks.size() - 1; blockIndex++) {
- // We skip the last block as it has no fallthrough. We also skip checking the entry block as
- // it has no predecessors and must define the initial position. Any subsequent block must be
- // statically reachable and thus have predecessors.
+ private static void removeTrivialGotoBlocks(IRCode code) {
+ for (int blockIndex = 1; blockIndex < code.blocks.size(); blockIndex++) {
+ // We skip checking the entry block as it has no predecessors and must define the initial
+ // position. Any subsequent block must be statically reachable and thus have predecessors.
BasicBlock currentBlock = code.blocks.get(blockIndex);
assert !currentBlock.getPredecessors().isEmpty();
if (currentBlock.size() != 2) {
@@ -366,35 +366,64 @@
if (debugPosition == null || exit == null || debugPosition.getPosition().isNone()) {
continue;
}
- BasicBlock nextBlock = code.blocks.get(blockIndex + 1);
- if (exit.getTarget() != nextBlock) {
- continue;
- }
- // The block is a trivial position block that falls through to the following.
- // If the position is equal to each predecessor block then the line is already active on
- // each entry to the fallthrough and it can be safely removed.
boolean allMatch = true;
Position position = debugPosition.getPosition();
for (BasicBlock pred : currentBlock.getPredecessors()) {
- // Do to the target == next check this cannot be a trivial loop.
- assert pred != currentBlock;
+ // If the block is a trivial loop it must remain.
+ if (pred == currentBlock) {
+ allMatch = false;
+ break;
+ }
+ // If the position is already active on each predecessor exit it can be safely removed
+ // (except for if/switch fallthrough cases guarded below).
Position predExit = pred.exit().getPosition();
if (!position.equals(predExit)) {
allMatch = false;
break;
}
+ // If this is a required fallthrough, we can only remove it if it targets the next block.
+ // Note that this could fail for a given block but then become valid after an intermediate
+ // block is removed. See the reset of blockIndex below for dealing with that case.
+ if (isFallthroughTargetToNonFallthroughTarget(pred, currentBlock, blockIndex, code)) {
+ allMatch = false;
+ break;
+ }
}
if (allMatch) {
currentBlock.removeInstruction(debugPosition);
- CodeRewriter.unlinkTrivialGotoBlock(currentBlock, nextBlock);
+ CodeRewriter.unlinkTrivialGotoBlock(currentBlock, exit.getTarget());
code.removeBlocks(Collections.singleton(currentBlock));
// Having removed the block at blockIndex, the previous block may now be a trivial
- // fallthrough. Rewind to that point and retry. This avoids iterating to a fixed point.
+ // fallthrough from an if/switch. Rewind to that point and retry. This avoids iterating to
+ // a fixed point.
blockIndex = Math.max(0, blockIndex - 2);
}
}
}
+ private static boolean isFallthroughTargetToNonFallthroughTarget(
+ BasicBlock pred, BasicBlock current, int blockIndex, IRCode code) {
+ JumpInstruction exit = pred.exit();
+ BasicBlock fallthrough;
+ if (exit.isIf()) {
+ fallthrough = exit.asIf().fallthroughBlock();
+ } else if (exit.isSwitch()) {
+ fallthrough = exit.asSwitch().fallthroughBlock();
+ } else {
+ return false;
+ }
+ if (fallthrough != current) {
+ return false;
+ }
+ if (blockIndex + 1 >= code.blocks.size()) {
+ // The current block is a if/switch fallthrough target and there is no next-block.
+ // The current jump is thus to a non-fallthrough block.
+ return true;
+ }
+ BasicBlock nextBlock = code.blocks.get(blockIndex + 1);
+ return current.exit().asGoto().getTarget() != nextBlock;
+ }
+
// Eliminates unneeded debug positions.
//
// After this pass all remaining debug positions mark places where we must ensure a materializing
@@ -407,7 +436,7 @@
// We must start by removing any blocks that are already trivial fallthrough blocks with no
// position change. With these removed it is then sound to make the fallthrough judgement when
// determining if a goto will materialize or not.
- removeTrivialFallthroughBlocks(code);
+ removeTrivialGotoBlocks(code);
// Current position known to have a materializing instruction associated with it.
Position currentMaterializedPosition = Position.none();
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 0c255da..f94cde1 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
@@ -51,6 +51,7 @@
import com.android.tools.r8.ir.desugar.nest.D8NestBasedAccessDesugaring;
import com.android.tools.r8.ir.optimize.AssertionsRewriter;
import com.android.tools.r8.ir.optimize.AssumeInserter;
+import com.android.tools.r8.ir.optimize.CheckNotNullConverter;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -61,7 +62,6 @@
import com.android.tools.r8.ir.optimize.IdempotentFunctionCallCanonicalizer;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.ir.optimize.MemberValuePropagation;
import com.android.tools.r8.ir.optimize.NaturalIntLoopRemover;
import com.android.tools.r8.ir.optimize.RedundantFieldLoadAndStoreElimination;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer;
@@ -76,6 +76,9 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.D8MemberValuePropagation;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.MemberValuePropagation;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.R8MemberValuePropagation;
import com.android.tools.r8.ir.optimize.outliner.Outliner;
import com.android.tools.r8.ir.optimize.string.StringBuilderOptimizer;
import com.android.tools.r8.ir.optimize.string.StringOptimizer;
@@ -128,7 +131,7 @@
public final CodeRewriter codeRewriter;
private final NaturalIntLoopRemover naturalIntLoopRemover = new NaturalIntLoopRemover();
private final ConstantCanonicalizer constantCanonicalizer;
- public final MemberValuePropagation memberValuePropagation;
+ public final MemberValuePropagation<?> memberValuePropagation;
private final LensCodeRewriter lensCodeRewriter;
private final Inliner inliner;
private final IdentifierNameStringMarker identifierNameStringMarker;
@@ -256,7 +259,7 @@
this.lensCodeRewriter = new LensCodeRewriter(appViewWithLiveness, enumUnboxer);
this.inliner = new Inliner(appViewWithLiveness, this, lensCodeRewriter);
this.outliner = Outliner.create(appViewWithLiveness);
- this.memberValuePropagation = new MemberValuePropagation(appViewWithLiveness);
+ this.memberValuePropagation = new R8MemberValuePropagation(appViewWithLiveness);
this.methodOptimizationInfoCollector =
new MethodOptimizationInfoCollector(appViewWithLiveness, this);
// TODO(b/214496607): Enable open/closed interfaces analysis.
@@ -276,6 +279,7 @@
this.enumValueOptimizer =
options.enableEnumValueOptimization ? new EnumValueOptimizer(appViewWithLiveness) : null;
} else {
+ AppView<AppInfo> appViewWithoutClassHierarchy = appView.withoutClassHierarchy();
this.assumeInserter = null;
this.classInliner = null;
this.dynamicTypeOptimization = null;
@@ -283,7 +287,10 @@
this.libraryMethodOverrideAnalysis = null;
this.inliner = null;
this.outliner = Outliner.empty();
- this.memberValuePropagation = null;
+ this.memberValuePropagation =
+ options.isGeneratingDex()
+ ? new D8MemberValuePropagation(appViewWithoutClassHierarchy)
+ : null;
this.lensCodeRewriter = null;
this.identifierNameStringMarker = null;
this.devirtualizer = null;
@@ -734,6 +741,7 @@
outliner.rewriteWithLens();
enumUnboxer.unboxEnums(appView, this, postMethodProcessorBuilder, executorService, feedback);
+ appView.unboxedEnums().checkEnumsUnboxed(appView);
GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens();
@@ -1176,6 +1184,7 @@
}
assertionsRewriter.run(method, code, deadCodeRemover, timing);
+ CheckNotNullConverter.runIfNecessary(appView, code);
if (serviceLoaderRewriter != null) {
assert appView.appInfo().hasLiveness();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 9c220f6..5011171 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
@@ -310,6 +311,7 @@
DexString name;
DexProto proto;
DexMethod method;
+ DexClassAndMethod definition;
// Objects
type = factory.objectsType;
@@ -351,14 +353,17 @@
addProvider(new InvokeRewriter(method, ObjectsMethodRewrites.rewriteRequireNonNull()));
// T Objects.requireNonNull(T obj, String message)
- name = factory.createString("requireNonNull");
- proto = factory.createProto(factory.objectType, factory.objectType, factory.stringType);
- method = factory.createMethod(type, proto, name);
- addProvider(
- new MethodGenerator(
- method,
- BackportedMethods::ObjectsMethods_requireNonNullMessage,
- "requireNonNullMessage"));
+ method = factory.objectsMethods.requireNonNullWithMessage;
+ definition = appView.enableWholeProgramOptimizations() ? appView.definitionFor(method) : null;
+ // Only backport requireNonNull(Object, String) if it is not matched by -convertchecknotnull.
+ // Otherwise all calls will be rewritten to getClass().
+ if (definition == null || !definition.getOptimizationInfo().isConvertCheckNotNull()) {
+ addProvider(
+ new MethodGenerator(
+ method,
+ BackportedMethods::ObjectsMethods_requireNonNullMessage,
+ "requireNonNullMessage"));
+ }
// String Objects.toString(Object o)
name = factory.createString("toString");
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 0d0dd97..5d774b2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -260,7 +260,12 @@
}
}
- private boolean isCovariantReturnTypeAnnotation(DexEncodedAnnotation annotation) {
+ public boolean isCovariantReturnTypeAnnotation(DexEncodedAnnotation annotation) {
+ return isCovariantReturnTypeAnnotation(annotation, factory);
+ }
+
+ public static boolean isCovariantReturnTypeAnnotation(
+ DexEncodedAnnotation annotation, DexItemFactory factory) {
return isCovariantReturnTypeAnnotation(annotation.type, factory);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
index a6384e1..b288d9a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/icce/AlwaysThrowingInstructionDesugaring.java
@@ -24,6 +24,7 @@
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringCollection;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.DesugarDescription;
+import com.android.tools.r8.ir.desugar.DesugarDescription.ScanCallback;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
@@ -105,46 +106,46 @@
getThrowInstructions(
appView,
invoke,
- resolutionResult,
localStackAllocator,
eventConsumer,
context,
- methodProcessingContext))
+ methodProcessingContext,
+ getMethodSynthesizerForThrowing(appView, invoke, resolutionResult, context)))
.build();
}
+ public static DesugarDescription computeInvokeAsThrowNSMERewrite(
+ AppView<?> appView, CfInvoke invoke, ScanCallback scanCallback) {
+ DesugarDescription.Builder builder =
+ DesugarDescription.builder()
+ .setDesugarRewrite(
+ (freshLocalProvider,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ dexItemFactory) ->
+ getThrowInstructions(
+ appView,
+ invoke,
+ localStackAllocator,
+ eventConsumer,
+ context,
+ methodProcessingContext,
+ UtilityMethodsForCodeOptimizations
+ ::synthesizeThrowNoSuchMethodErrorMethod));
+ builder.addScanEffect(scanCallback);
+ return builder.build();
+ }
+
private static Collection<CfInstruction> getThrowInstructions(
AppView<?> appView,
CfInvoke invoke,
- MethodResolutionResult resolutionResult,
LocalStackAllocator localStackAllocator,
CfInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context,
- MethodProcessingContext methodProcessingContext) {
- MethodSynthesizerConsumer methodSynthesizerConsumer = null;
- if (resolutionResult == null) {
- methodSynthesizerConsumer =
- UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
- } else if (resolutionResult.isSingleResolution()) {
- if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
- methodSynthesizerConsumer =
- UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
- }
- } else if (resolutionResult.isFailedResolution()) {
- FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
- AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
- if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
- methodSynthesizerConsumer =
- UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
- } else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
- methodSynthesizerConsumer =
- UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
- } else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
- methodSynthesizerConsumer =
- UtilityMethodsForCodeOptimizations::synthesizeThrowIncompatibleClassChangeErrorMethod;
- }
- }
-
+ MethodProcessingContext methodProcessingContext,
+ MethodSynthesizerConsumer methodSynthesizerConsumer) {
if (methodSynthesizerConsumer == null) {
assert false;
return null;
@@ -198,4 +199,32 @@
}
return replacement;
}
+
+ private static MethodSynthesizerConsumer getMethodSynthesizerForThrowing(
+ AppView<?> appView,
+ CfInvoke invoke,
+ MethodResolutionResult resolutionResult,
+ ProgramMethod context) {
+ if (resolutionResult == null) {
+ return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+ } else if (resolutionResult.isSingleResolution()) {
+ if (resolutionResult.getResolvedMethod().isStatic() != invoke.isInvokeStatic()) {
+ return UtilityMethodsForCodeOptimizations
+ ::synthesizeThrowIncompatibleClassChangeErrorMethod;
+ }
+ } else if (resolutionResult.isFailedResolution()) {
+ FailedResolutionResult failedResolutionResult = resolutionResult.asFailedResolution();
+ AppInfoWithClassHierarchy appInfo = appView.appInfoForDesugaring();
+ if (failedResolutionResult.isIllegalAccessErrorResult(context.getHolder(), appInfo)) {
+ return UtilityMethodsForCodeOptimizations::synthesizeThrowIllegalAccessErrorMethod;
+ } else if (failedResolutionResult.isNoSuchMethodErrorResult(context.getHolder(), appInfo)) {
+ return UtilityMethodsForCodeOptimizations::synthesizeThrowNoSuchMethodErrorMethod;
+ } else if (failedResolutionResult.isIncompatibleClassChangeErrorResult()) {
+ return UtilityMethodsForCodeOptimizations
+ ::synthesizeThrowIncompatibleClassChangeErrorMethod;
+ }
+ }
+
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
index 695af34..c764377 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -78,6 +78,7 @@
emulatedInterface.getSourceFile(),
null,
Collections.emptyList(),
+ Collections.emptyList(),
null, // Note that we clear the enclosing and inner class attributes.
Collections.emptyList(),
emulatedInterface.getClassSignature(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index bc8ebd0..8e8541b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -44,6 +44,7 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.MethodPosition;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.structural.Ordered;
import com.google.common.collect.Iterables;
@@ -711,7 +712,23 @@
}
}
- return computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
+ DesugarDescription emulatedInterfaceDesugaring =
+ computeEmulatedInterfaceInvokeSpecial(clazz, invokedMethod, context);
+ if (!emulatedInterfaceDesugaring.needsDesugaring() && context.isDefaultMethod()) {
+ return AlwaysThrowingInstructionDesugaring.computeInvokeAsThrowNSMERewrite(
+ appView,
+ invoke,
+ () ->
+ appView
+ .reporter()
+ .warning(
+ new StringDiagnostic(
+ "Interface method desugaring has inserted NoSuchMethodError replacing a"
+ + " super call in "
+ + context.toSourceString(),
+ context.getOrigin())));
+ }
+ return emulatedInterfaceDesugaring;
}
private DesugarDescription computeEmulatedInterfaceInvokeSpecial(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
index df9d508..ed206d4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeInserter.java
@@ -240,9 +240,7 @@
if (invoke.hasUsedOutValue() && invoke.getOutType().isReferenceType()) {
AssumeInfo assumeInfo =
AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget);
- if (assumeInfo != null
- && assumeInfo.hasReturnInfo()
- && assumeInfo.getReturnInfo().isNonNull()) {
+ if (assumeInfo.getAssumeType().getNullability().isDefinitelyNotNull()) {
assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(invoke, invoke.outValue());
}
}
@@ -294,10 +292,8 @@
DexClassAndField field = resolutionResult.getResolutionPair();
if (field.getType().isReferenceType()) {
- AssumeInfo assumeInfo = AssumeInfoLookup.lookupAssumeInfo(appView, field);
- if (assumeInfo != null
- && assumeInfo.hasReturnInfo()
- && assumeInfo.getReturnInfo().isNonNull()) {
+ AssumeInfo assumeInfo = appView.getAssumeInfoCollection().get(field);
+ if (assumeInfo.getAssumeType().getNullability().isDefinitelyNotNull()) {
assumedValuesBuilder.addNonNullValueKnownToDominateAllUsers(fieldGet, fieldGet.outValue());
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java b/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java
new file mode 100644
index 0000000..9cffada
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CheckNotNullConverter.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlockIterator;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Value;
+
+public class CheckNotNullConverter {
+
+ public static void runIfNecessary(AppView<?> appView, IRCode code) {
+ if (appView.enableWholeProgramOptimizations()) {
+ run(appView.withClassHierarchy(), code);
+ }
+ }
+
+ /**
+ * Replace all calls to methods marked as a check-not-null method by a call to Object.getClass(),
+ * using the first argument as the receiver for the new call.
+ *
+ * <p>If the invoke has an out-value, the out-value is replaced by the first argument to allow
+ * removing the invoke.
+ */
+ private static void run(AppView<? extends AppInfoWithClassHierarchy> appView, IRCode code) {
+ BasicBlockIterator blockIterator = code.listIterator();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ InstructionListIterator instructionIterator = block.listIterator(code);
+ while (instructionIterator.hasNext()) {
+ Instruction instruction = instructionIterator.next();
+ if (instruction.isInvokeMethod()) {
+ rewriteInvoke(appView, code, instructionIterator, instruction.asInvokeMethod());
+ }
+ }
+ }
+ }
+
+ private static void rewriteInvoke(
+ AppView<? extends AppInfoWithClassHierarchy> appView,
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke) {
+ ProgramMethod context = code.context();
+ DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
+ if (singleTarget == null || !singleTarget.getOptimizationInfo().isConvertCheckNotNull()) {
+ return;
+ }
+ Value checkNotNullValue = invoke.getFirstNonReceiverArgument();
+ if (invoke.hasUsedOutValue()) {
+ invoke.outValue().replaceUsers(checkNotNullValue);
+ }
+ if (checkNotNullValue.getType().nullability().isDefinitelyNotNull()) {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ } else {
+ instructionIterator.replaceCurrentInstructionWithNullCheck(appView, checkNotNullValue);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 107bd20..75c275f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -43,6 +43,7 @@
import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.BooleanUtils;
@@ -339,16 +340,16 @@
SingleResolutionResult<?> resolutionResult,
ProgramMethod singleTarget,
WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
- AppInfoWithLiveness appInfo = appView.appInfo();
DexMethod singleTargetReference = singleTarget.getReference();
if (!appView.getKeepInfo(singleTarget).isInliningAllowed(appView.options())) {
whyAreYouNotInliningReporter.reportPinned();
return true;
}
- if (appInfo.noSideEffects.containsKey(invoke.getInvokedMethod())
- || appInfo.noSideEffects.containsKey(resolutionResult.getResolvedMethod().getReference())
- || appInfo.noSideEffects.containsKey(singleTargetReference)) {
+ AssumeInfoCollection assumeInfoCollection = appView.getAssumeInfoCollection();
+ if (assumeInfoCollection.isSideEffectFree(invoke.getInvokedMethod())
+ || assumeInfoCollection.isSideEffectFree(resolutionResult.getResolutionPair())
+ || assumeInfoCollection.isSideEffectFree(singleTargetReference)) {
return !singleTarget.getDefinition().getOptimizationInfo().forceInline();
}
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 4be71a5..f0980f3 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
@@ -58,6 +58,7 @@
import com.android.tools.r8.ir.optimize.inliner.InliningReasonStrategy;
import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.R8MemberValuePropagation;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.utils.ConsumerUtils;
@@ -1215,7 +1216,7 @@
ListIterator<BasicBlock> blockIterator,
Set<BasicBlock> inlineeBlocks) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
- new MemberValuePropagation(appView)
+ new R8MemberValuePropagation(appView)
.run(code, blockIterator, affectedValues, inlineeBlocks::contains);
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
index 264fc7b..00c4f50 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
@@ -4,13 +4,18 @@
package com.android.tools.r8.ir.optimize.enums;
+import com.android.tools.r8.errors.CheckEnumUnboxedDiagnostic;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Set;
public class EnumDataMap {
@@ -24,6 +29,23 @@
this.map = map;
}
+ public void checkEnumsUnboxed(AppView<AppInfoWithLiveness> appView) {
+ List<DexProgramClass> failed = new ArrayList<>();
+ for (DexProgramClass clazz : appView.appInfo().classes()) {
+ if (clazz.isEnum()) {
+ if (appView.getKeepInfo(clazz).isCheckEnumUnboxedEnabled(appView.options())
+ && !isUnboxedEnum(clazz)) {
+ failed.add(clazz);
+ }
+ }
+ }
+ if (!failed.isEmpty()) {
+ CheckEnumUnboxedDiagnostic diagnostic =
+ CheckEnumUnboxedDiagnostic.builder().addFailedEnums(failed).build();
+ throw appView.reporter().fatalError(diagnostic);
+ }
+ }
+
public boolean isUnboxedEnum(DexProgramClass clazz) {
return isUnboxedEnum(clazz.getType());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index c9f6819..f518d5e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -147,6 +147,11 @@
}
@Override
+ public boolean isConvertCheckNotNull() {
+ return false;
+ }
+
+ @Override
public boolean isInitializerEnablingJavaVmAssertions() {
return UNKNOWN_INITIALIZER_ENABLING_JAVA_ASSERTIONS;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index c36ee4d..c4e8e3a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -68,6 +68,8 @@
public abstract InstanceInitializerInfo getInstanceInitializerInfo(InvokeDirect invoke);
+ public abstract boolean isConvertCheckNotNull();
+
public abstract boolean isInitializerEnablingJavaVmAssertions();
public abstract AbstractValue getAbstractReturnValue();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
index df68f40..3491800 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableMethodOptimizationInfo.java
@@ -44,6 +44,7 @@
DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
private ClassInlinerMethodConstraint classInlinerConstraint =
ClassInlinerMethodConstraint.alwaysFalse();
+ private boolean convertCheckNotNull = false;
private EnumUnboxerMethodClassification enumUnboxerMethodClassification =
EnumUnboxerMethodClassification.unknown();
private DynamicType dynamicType = DynamicType.unknown();
@@ -266,6 +267,10 @@
this.classInlinerConstraint = ClassInlinerMethodConstraint.alwaysFalse();
}
+ void setConvertCheckNotNull() {
+ this.convertCheckNotNull = true;
+ }
+
@Override
public EnumUnboxerMethodClassification getEnumUnboxerMethodClassification() {
return enumUnboxerMethodClassification;
@@ -454,6 +459,11 @@
}
@Override
+ public boolean isConvertCheckNotNull() {
+ return convertCheckNotNull;
+ }
+
+ @Override
public boolean isInitializerEnablingJavaVmAssertions() {
return isFlagSet(INITIALIZER_ENABLING_JAVA_ASSERTIONS_FLAG);
}
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 7a6a3ce..c766497 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
@@ -5,6 +5,7 @@
package com.android.tools.r8.ir.optimize.info;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
@@ -152,6 +153,10 @@
// Ignored.
}
+ public void setConvertCheckNotNull(DexClassAndMethod method) {
+ method.getDefinition().getMutableOptimizationInfo().setConvertCheckNotNull();
+ }
+
@Override
public void setEnumUnboxerMethodClassification(
ProgramMethod method, EnumUnboxerMethodClassification enumUnboxerMethodClassification) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
index 37ca53d..2cf8495 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LogMethodOptimizer.java
@@ -110,51 +110,67 @@
DexClassAndMethod singleTarget,
Set<Value> affectedValues,
Set<BasicBlock> blocksToRemove) {
+ // Replace Android logging statements like Log.w(...) and Log.IsLoggable(..., WARNING) at or
+ // below a certain logging level by false.
+ int logLevel = getLogLevel(invoke, singleTarget);
int maxRemovedAndroidLogLevel =
appView.options().getProguardConfiguration().getMaxRemovedAndroidLogLevel();
- if (singleTarget.getReference() == isLoggableMethod) {
- Value logLevelValue = invoke.arguments().get(1).getAliasedValue();
- if (!logLevelValue.isPhi() && !logLevelValue.hasLocalInfo()) {
- Instruction definition = logLevelValue.definition;
- if (definition.isConstNumber()) {
- int logLevel = definition.asConstNumber().getIntValue();
- replaceInvokeWithConstNumber(
- code, instructionIterator, invoke, maxRemovedAndroidLogLevel >= logLevel ? 0 : 1);
- }
- }
- } else if (singleTarget.getReference() == vMethod) {
- if (maxRemovedAndroidLogLevel >= VERBOSE) {
- replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
- }
- } else if (singleTarget.getReference() == dMethod) {
- if (maxRemovedAndroidLogLevel >= DEBUG) {
- replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
- }
- } else if (singleTarget.getReference() == iMethod) {
- if (maxRemovedAndroidLogLevel >= INFO) {
- replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
- }
- } else if (singleTarget.getReference() == wMethod) {
- if (maxRemovedAndroidLogLevel >= WARN) {
- replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
- }
- } else if (singleTarget.getReference() == eMethod) {
- if (maxRemovedAndroidLogLevel >= ERROR) {
- replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
- }
- } else if (singleTarget.getReference() == wtfMethod) {
- if (maxRemovedAndroidLogLevel >= ASSERT) {
- replaceInvokeWithConstNumber(code, instructionIterator, invoke, 0);
- }
+ if (VERBOSE <= logLevel && logLevel <= maxRemovedAndroidLogLevel) {
+ instructionIterator.replaceCurrentInstructionWithConstFalse(code);
}
}
- private void replaceInvokeWithConstNumber(
- IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke, int value) {
- if (invoke.hasOutValue() && invoke.outValue().hasAnyUsers()) {
- instructionIterator.replaceCurrentInstructionWithConstInt(code, value);
- } else {
- instructionIterator.removeOrReplaceByDebugLocalRead();
+ /**
+ * @return The log level of the given invoke if it is a call to an android.util.Log method and the
+ * log level can be determined, otherwise returns -1.
+ */
+ private int getLogLevel(InvokeMethod invoke, DexClassAndMethod singleTarget) {
+ DexMethod singleTargetReference = singleTarget.getReference();
+ switch (singleTargetReference.getName().getFirstByteAsChar()) {
+ case 'd':
+ if (singleTargetReference == dMethod) {
+ return DEBUG;
+ }
+ break;
+ case 'e':
+ if (singleTargetReference == eMethod) {
+ return ERROR;
+ }
+ break;
+ case 'i':
+ if (singleTargetReference == iMethod) {
+ return INFO;
+ }
+ if (singleTargetReference == isLoggableMethod) {
+ Value logLevelValue = invoke.arguments().get(1).getAliasedValue();
+ if (!logLevelValue.isPhi() && !logLevelValue.hasLocalInfo()) {
+ Instruction definition = logLevelValue.getDefinition();
+ if (definition.isConstNumber()) {
+ int logLevel = definition.asConstNumber().getIntValue();
+ if (VERBOSE <= logLevel && logLevel <= ASSERT) {
+ return logLevel;
+ }
+ assert false;
+ }
+ }
+ }
+ break;
+ case 'v':
+ if (singleTargetReference == vMethod) {
+ return VERBOSE;
+ }
+ break;
+ case 'w':
+ if (singleTargetReference == wMethod) {
+ return WARN;
+ }
+ if (singleTargetReference == wtfMethod) {
+ return ASSERT;
+ }
+ break;
+ default:
+ break;
}
+ return -1;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
index bd7edf7..0c65d04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
@@ -37,14 +37,14 @@
return false;
}
+ // Check if there is an -assumenosideeffects rule for the toString() method.
+ if (appView.getAssumeInfoCollection().isSideEffectFree(toStringMethodReference)) {
+ return false;
+ }
+
if (appView.appInfo().hasLiveness()) {
AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
- // Check if there is an -assumenosideeffects rule for the toString() method.
- if (appInfo.isAssumeNoSideEffectsMethod(toStringMethodReference)) {
- return false;
- }
-
// Check if this is a program class with a toString() method that does not have side effects.
DexClass clazz = appInfo.definitionFor(classType);
if (clazz != null && clazz.isEffectivelyFinal(appView)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java
new file mode 100644
index 0000000..020d86e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/D8MemberValuePropagation.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, 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.membervaluepropagation;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+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.membervaluepropagation.assume.AssumeInfo;
+import java.util.ListIterator;
+import java.util.Set;
+
+public class D8MemberValuePropagation extends MemberValuePropagation<AppInfo> {
+
+ public D8MemberValuePropagation(AppView<AppInfo> appView) {
+ super(appView);
+ }
+
+ @Override
+ void rewriteArrayGet(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ ArrayGet arrayGet) {
+ // Intentionally empty.
+ }
+
+ @Override
+ void rewriteInstanceGet(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ InstanceGet current) {
+ // Intentionally empty.
+ }
+
+ @Override
+ void rewriteInstancePut(IRCode code, InstructionListIterator iterator, InstancePut current) {
+ // Intentionally empty.
+ }
+
+ @Override
+ void rewriteInvokeMethod(
+ IRCode code,
+ ProgramMethod context,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ InvokeMethod invoke) {
+ // Intentionally empty.
+ }
+
+ @Override
+ void rewriteStaticGet(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ StaticGet current) {
+ AssumeInfo assumeInfo = appView.getAssumeInfoCollection().get(current.getField());
+ applyAssumeInfo(code, affectedValues, blocks, iterator, current, assumeInfo);
+ }
+
+ @Override
+ void rewriteStaticPut(IRCode code, InstructionListIterator iterator, StaticPut current) {
+ // Intentionally empty.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java
new file mode 100644
index 0000000..14a54b1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MemberValuePropagation.java
@@ -0,0 +1,234 @@
+// Copyright (c) 2022, 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.membervaluepropagation;
+
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
+import static com.google.common.base.Predicates.alwaysTrue;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+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.SingleValue;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Position;
+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.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.utils.IteratorUtils;
+import com.google.common.collect.Sets;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.function.Predicate;
+
+public abstract class MemberValuePropagation<T extends AppInfo> {
+
+ final AppView<T> appView;
+
+ MemberValuePropagation(AppView<T> appView) {
+ this.appView = appView;
+ }
+
+ /**
+ * Replace invoke targets and field accesses with constant values where possible.
+ *
+ * <p>Also assigns value ranges to values where possible.
+ */
+ public void run(IRCode code) {
+ IRMetadata metadata = code.metadata();
+ if (!metadata.mayHaveFieldInstruction() && !metadata.mayHaveInvokeMethod()) {
+ return;
+ }
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
+ run(code, code.listIterator(), affectedValues, alwaysTrue());
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
+ }
+ assert code.isConsistentSSA(appView);
+ assert code.verifyTypes(appView);
+ }
+
+ public void run(
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ Set<Value> affectedValues,
+ Predicate<BasicBlock> blockTester) {
+ ProgramMethod context = code.context();
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ if (!blockTester.test(block)) {
+ continue;
+ }
+ InstructionListIterator iterator = block.listIterator(code);
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ switch (current.opcode()) {
+ case ARRAY_GET:
+ rewriteArrayGet(code, affectedValues, blockIterator, iterator, current.asArrayGet());
+ break;
+ case INSTANCE_GET:
+ rewriteInstanceGet(
+ code, affectedValues, blockIterator, iterator, current.asInstanceGet());
+ break;
+ case INSTANCE_PUT:
+ rewriteInstancePut(code, iterator, current.asInstancePut());
+ break;
+ case INVOKE_DIRECT:
+ case INVOKE_INTERFACE:
+ case INVOKE_STATIC:
+ case INVOKE_SUPER:
+ case INVOKE_VIRTUAL:
+ rewriteInvokeMethod(
+ code, context, affectedValues, blockIterator, iterator, current.asInvokeMethod());
+ break;
+ case STATIC_GET:
+ rewriteStaticGet(code, affectedValues, blockIterator, iterator, current.asStaticGet());
+ break;
+ case STATIC_PUT:
+ rewriteStaticPut(code, iterator, current.asStaticPut());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ abstract void rewriteArrayGet(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ ArrayGet arrayGet);
+
+ abstract void rewriteInstanceGet(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ InstanceGet current);
+
+ abstract void rewriteInstancePut(
+ IRCode code, InstructionListIterator iterator, InstancePut current);
+
+ abstract void rewriteInvokeMethod(
+ IRCode code,
+ ProgramMethod context,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ InvokeMethod invoke);
+
+ abstract void rewriteStaticGet(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ StaticGet current);
+
+ abstract void rewriteStaticPut(IRCode code, InstructionListIterator iterator, StaticPut current);
+
+ boolean applyAssumeInfo(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ Instruction current,
+ AssumeInfo assumeInfo) {
+ // Remove if side effect free.
+ if (assumeInfo.isSideEffectFree() && current.hasUnusedOutValue()) {
+ iterator.removeOrReplaceByDebugLocalRead();
+ return true;
+ }
+
+ // Set value range if any.
+ if (current.hasOutValue()
+ && current.getOutType().isPrimitiveType()
+ && assumeInfo.getAssumeValue().isNumberFromIntervalValue()) {
+ current.outValue().setValueRange(assumeInfo.getAssumeValue().asNumberFromIntervalValue());
+ }
+
+ // Insert replacement if any.
+ Instruction replacement = createReplacementFromAssumeInfo(assumeInfo, code, current);
+ if (replacement == null) {
+ return false;
+ }
+
+ affectedValues.addAll(current.outValue().affectedValues());
+ if (assumeInfo.isSideEffectFree()) {
+ iterator.replaceCurrentInstruction(replacement);
+ return true;
+ }
+ BasicBlock block = current.getBlock();
+ Position position = current.getPosition();
+ if (current.hasOutValue()) {
+ assert replacement.outValue() != null;
+ current.outValue().replaceUsers(replacement.outValue());
+ }
+ if (current.isInstanceGet()) {
+ iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
+ } else if (current.isStaticGet()) {
+ StaticGet staticGet = current.asStaticGet();
+ iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
+ appView, code, staticGet.getField().holder);
+ }
+ replacement.setPosition(position);
+ if (block.hasCatchHandlers()) {
+ BasicBlock splitBlock = iterator.split(code, blocks);
+ splitBlock.listIterator(code).add(replacement);
+
+ // Process the materialized value.
+ blocks.previous();
+ assert !iterator.hasNext();
+ assert IteratorUtils.peekNext(blocks) == splitBlock;
+
+ return true;
+ } else {
+ iterator.add(replacement);
+ }
+
+ // Process the materialized value.
+ iterator.previous();
+ assert iterator.peekNext() == replacement;
+
+ return true;
+ }
+
+ private Instruction createReplacementFromAssumeInfo(
+ AssumeInfo assumeInfo, IRCode code, Instruction instruction) {
+ if (assumeInfo.getAssumeValue().isUnknown()) {
+ return null;
+ }
+
+ AbstractValue assumeValue = assumeInfo.getAssumeValue();
+ if (assumeValue.isSingleValue()) {
+ SingleValue singleValue = assumeValue.asSingleValue();
+ if (singleValue.isMaterializableInContext(appView, code.context())) {
+ return singleValue.createMaterializingInstruction(appView, code, instruction);
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
similarity index 61%
rename from src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
rename to src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
index d572d2f..bc33a3a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/R8MemberValuePropagation.java
@@ -1,12 +1,8 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// Copyright (c) 2022, 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;
+package com.android.tools.r8.ir.optimize.membervaluepropagation;
-import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.google.common.base.Predicates.alwaysTrue;
-
-import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
@@ -18,7 +14,6 @@
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.analysis.value.SingleValue;
@@ -27,7 +22,7 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.IRMetadata;
+import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -41,33 +36,20 @@
import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
-import com.android.tools.r8.utils.IteratorUtils;
-import com.android.tools.r8.utils.Reporter;
-import com.android.tools.r8.utils.StringDiagnostic;
-import com.google.common.collect.Sets;
import java.util.ListIterator;
import java.util.Set;
-import java.util.function.Predicate;
-public class MemberValuePropagation {
+public class R8MemberValuePropagation extends MemberValuePropagation<AppInfoWithLiveness> {
private static final OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
- private final AppView<AppInfoWithLiveness> appView;
- private final Reporter reporter;
-
- // Fields for which we have reported warnings to due Proguard configuration rules.
- private final Set<DexField> warnedFields = Sets.newIdentityHashSet();
-
- public MemberValuePropagation(AppView<AppInfoWithLiveness> appView) {
- this.appView = appView;
- this.reporter = appView.options().reporter;
+ public R8MemberValuePropagation(AppView<AppInfoWithLiveness> appView) {
+ super(appView);
}
- private void rewriteArrayGet(
+ @Override
+ void rewriteArrayGet(
IRCode code,
- ProgramMethod context,
Set<Value> affectedValues,
ListIterator<BasicBlock> blocks,
InstructionListIterator iterator,
@@ -126,153 +108,18 @@
if (field.isProgramField()) {
return appView.appInfo().mayPropagateValueFor(appView, field.getReference());
}
- return appView.appInfo().assumedValues.containsKey(field.getReference())
- || appView.appInfo().noSideEffects.containsKey(field.getReference());
+ return appView.getAssumeInfoCollection().contains(field);
}
private boolean mayPropagateValueFor(DexClassAndMethod method) {
if (method.isProgramMethod()) {
return appView.appInfo().mayPropagateValueFor(appView, method.getReference());
}
- return appView.appInfo().assumedValues.containsKey(method.getReference())
- || appView.appInfo().noSideEffects.containsKey(method.getReference());
+ return appView.getAssumeInfoCollection().contains(method);
}
- private Instruction createReplacementFromAssumeInfo(
- AssumeInfo assumeInfo, IRCode code, Instruction instruction) {
- if (!assumeInfo.hasReturnInfo()) {
- return null;
- }
-
- ProguardMemberRuleReturnValue returnValueRule = assumeInfo.getReturnInfo();
-
- // Check if this value can be assumed constant.
- if (returnValueRule.isSingleValue()) {
- if (instruction.getOutType().isReferenceType()) {
- if (returnValueRule.getSingleValue() == 0) {
- return appView
- .abstractValueFactory()
- .createNullValue()
- .createMaterializingInstruction(appView, code, instruction);
- }
- return null;
- }
- return appView.abstractValueFactory()
- .createSingleNumberValue(returnValueRule.getSingleValue())
- .createMaterializingInstruction(appView, code, instruction);
- }
-
- if (returnValueRule.isField()) {
- DexField field = returnValueRule.getField();
- assert instruction.getOutType() == TypeElement.fromDexType(field.type, maybeNull(), appView);
-
- DexClassAndField staticField = appView.appInfo().lookupStaticTarget(field);
- if (staticField == null) {
- if (warnedFields.add(field)) {
- reporter.warning(
- new StringDiagnostic(
- "Field `"
- + field.toSourceString()
- + "` is used in an -assumevalues rule but does not exist.",
- code.origin));
- }
- return null;
- }
-
- if (AccessControl.isMemberAccessible(
- staticField, staticField.getHolder(), code.context(), appView)
- .isTrue()) {
- return StaticGet.builder()
- .setField(field)
- .setFreshOutValue(code, field.getTypeElement(appView), instruction.getLocalInfo())
- .build();
- }
-
- Instruction replacement =
- staticField
- .getDefinition()
- .valueAsConstInstruction(code, instruction.getLocalInfo(), appView);
- if (replacement == null) {
- reporter.warning(
- new StringDiagnostic(
- "Unable to apply the rule `"
- + returnValueRule.toString()
- + "`: Could not determine the value of field `"
- + field.toSourceString()
- + "`",
- code.origin));
- return null;
- }
- return replacement;
- }
-
- return null;
- }
-
- private void setValueRangeFromAssumeInfo(AssumeInfo assumeInfo, Value value) {
- if (assumeInfo.hasReturnInfo() && assumeInfo.getReturnInfo().isValueRange()) {
- assert !assumeInfo.getReturnInfo().isSingleValue();
- value.setValueRange(assumeInfo.getReturnInfo().getValueRange());
- }
- }
-
- private boolean applyAssumeInfoIfPossible(
- IRCode code,
- Set<Value> affectedValues,
- ListIterator<BasicBlock> blocks,
- InstructionListIterator iterator,
- Instruction current,
- AssumeInfo assumeInfo) {
- Instruction replacement = createReplacementFromAssumeInfo(assumeInfo, code, current);
- if (replacement == null) {
- // Check to see if a value range can be assumed.
- if (current.getOutType().isPrimitiveType()) {
- setValueRangeFromAssumeInfo(assumeInfo, current.outValue());
- }
- return false;
- }
- affectedValues.addAll(current.outValue().affectedValues());
- if (assumeInfo.isAssumeNoSideEffects()) {
- iterator.replaceCurrentInstruction(replacement);
- } else {
- assert assumeInfo.isAssumeValues();
- BasicBlock block = current.getBlock();
- Position position = current.getPosition();
- if (current.hasOutValue()) {
- assert replacement.outValue() != null;
- current.outValue().replaceUsers(replacement.outValue());
- }
- if (current.isInstanceGet()) {
- iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
- } else if (current.isStaticGet()) {
- StaticGet staticGet = current.asStaticGet();
- iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
- appView, code, staticGet.getField().holder);
- }
- replacement.setPosition(position);
- if (block.hasCatchHandlers()) {
- BasicBlock splitBlock = iterator.split(code, blocks);
- splitBlock.listIterator(code).add(replacement);
-
- // Process the materialized value.
- blocks.previous();
- assert !iterator.hasNext();
- assert IteratorUtils.peekNext(blocks) == splitBlock;
-
- return true;
- } else {
- iterator.add(replacement);
- }
- }
-
- // Process the materialized value.
- iterator.previous();
- assert iterator.peekNext() == replacement;
-
- return true;
- }
-
- private void rewriteInvokeMethodWithConstantValues(
+ @Override
+ void rewriteInvokeMethod(
IRCode code,
ProgramMethod context,
Set<Value> affectedValues,
@@ -300,8 +147,7 @@
DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, resolutionResult, singleTarget);
- if (lookup != null
- && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, invoke, lookup)) {
+ if (applyAssumeInfo(code, affectedValues, blocks, iterator, invoke, lookup)) {
return;
}
@@ -358,7 +204,27 @@
}
}
- private void rewriteFieldGetWithConstantValues(
+ @Override
+ void rewriteInstanceGet(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ InstanceGet current) {
+ rewriteFieldGet(code, affectedValues, blocks, iterator, current);
+ }
+
+ @Override
+ void rewriteStaticGet(
+ IRCode code,
+ Set<Value> affectedValues,
+ ListIterator<BasicBlock> blocks,
+ InstructionListIterator iterator,
+ StaticGet current) {
+ rewriteFieldGet(code, affectedValues, blocks, iterator, current);
+ }
+
+ private void rewriteFieldGet(
IRCode code,
Set<Value> affectedValues,
ListIterator<BasicBlock> blocks,
@@ -367,7 +233,7 @@
DexField field = current.getField();
// TODO(b/123857022): Should be able to use definitionFor().
- SingleFieldResolutionResult resolutionResult =
+ SingleFieldResolutionResult<?> resolutionResult =
appView.appInfo().resolveField(field).asSingleFieldResolutionResult();
if (resolutionResult == null) {
boolean replaceCurrentInstructionWithConstNull =
@@ -397,9 +263,8 @@
}
// Check if there is a Proguard configuration rule that specifies the value of the field.
- AssumeInfo lookup = AssumeInfoLookup.lookupAssumeInfo(appView, target);
- if (lookup != null
- && applyAssumeInfoIfPossible(code, affectedValues, blocks, iterator, current, lookup)) {
+ AssumeInfo lookup = appView.getAssumeInfoCollection().get(target);
+ if (applyAssumeInfo(code, affectedValues, blocks, iterator, current, lookup)) {
return;
}
@@ -470,6 +335,11 @@
}
}
+ @Override
+ void rewriteInstancePut(IRCode code, InstructionListIterator iterator, InstancePut current) {
+ replaceInstancePutByNullCheckIfNeverRead(code, iterator, current);
+ }
+
private void replaceInstancePutByNullCheckIfNeverRead(
IRCode code, InstructionListIterator iterator, InstancePut current) {
DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
@@ -487,6 +357,11 @@
iterator.replaceCurrentInstructionByNullCheckIfPossible(appView, code.context());
}
+ @Override
+ void rewriteStaticPut(IRCode code, InstructionListIterator iterator, StaticPut current) {
+ replaceStaticPutByInitClassIfNeverRead(code, iterator, current);
+ }
+
private void replaceStaticPutByInitClassIfNeverRead(
IRCode code, InstructionListIterator iterator, StaticPut current) {
DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
@@ -504,55 +379,4 @@
iterator.removeOrReplaceCurrentInstructionByInitClassIfPossible(
appView, code, field.getHolderType());
}
-
- /**
- * Replace invoke targets and field accesses with constant values where possible.
- *
- * <p>Also assigns value ranges to values where possible.
- */
- public void run(IRCode code) {
- IRMetadata metadata = code.metadata();
- if (!metadata.mayHaveFieldInstruction() && !metadata.mayHaveInvokeMethod()) {
- return;
- }
- Set<Value> affectedValues = Sets.newIdentityHashSet();
- run(code, code.listIterator(), affectedValues, alwaysTrue());
- if (!affectedValues.isEmpty()) {
- new TypeAnalysis(appView).narrowing(affectedValues);
- }
- assert code.isConsistentSSA(appView);
- assert code.verifyTypes(appView);
- }
-
- public void run(
- IRCode code,
- ListIterator<BasicBlock> blockIterator,
- Set<Value> affectedValues,
- Predicate<BasicBlock> blockTester) {
- ProgramMethod context = code.context();
- while (blockIterator.hasNext()) {
- BasicBlock block = blockIterator.next();
- if (!blockTester.test(block)) {
- continue;
- }
- InstructionListIterator iterator = block.listIterator(code);
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (current.isArrayGet()) {
- rewriteArrayGet(
- code, context, affectedValues, blockIterator, iterator, current.asArrayGet());
- } else if (current.isInvokeMethod()) {
- rewriteInvokeMethodWithConstantValues(
- code, context, affectedValues, blockIterator, iterator, current.asInvokeMethod());
- } else if (current.isFieldGet()) {
- rewriteFieldGetWithConstantValues(
- code, affectedValues, blockIterator, iterator, current.asFieldInstruction());
- } else if (current.isInstancePut()) {
- replaceInstancePutByNullCheckIfNeverRead(code, iterator, current.asInstancePut());
- } else if (current.isStaticPut()) {
- replaceStaticPutByInitClassIfNeverRead(code, iterator, current.asStaticPut());
- }
- }
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
index a704abb..21d8d33 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfo.java
@@ -4,64 +4,188 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation.assume;
-import com.android.tools.r8.shaking.ProguardMemberRule;
-import com.android.tools.r8.shaking.ProguardMemberRuleReturnValue;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import java.util.Objects;
public class AssumeInfo {
- public enum AssumeType {
- ASSUME_NO_SIDE_EFFECTS,
- ASSUME_VALUES;
+ private static final AssumeInfo EMPTY =
+ new AssumeInfo(DynamicType.unknown(), AbstractValue.unknown(), false);
- AssumeType meet(AssumeType type) {
- return this == ASSUME_NO_SIDE_EFFECTS || type == ASSUME_NO_SIDE_EFFECTS
- ? ASSUME_NO_SIDE_EFFECTS
- : ASSUME_VALUES;
+ private final DynamicType assumeType;
+ private final AbstractValue assumeValue;
+ private final boolean isSideEffectFree;
+
+ private AssumeInfo(DynamicType assumeType, AbstractValue assumeValue, boolean isSideEffectFree) {
+ this.assumeType = assumeType;
+ this.assumeValue = assumeValue;
+ this.isSideEffectFree = isSideEffectFree;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static AssumeInfo create(
+ DynamicType assumeType, AbstractValue assumeValue, boolean isSideEffectFree) {
+ return assumeType.isUnknown() && assumeValue.isUnknown() && !isSideEffectFree
+ ? empty()
+ : new AssumeInfo(assumeType, assumeValue, isSideEffectFree);
+ }
+
+ public static AssumeInfo empty() {
+ return EMPTY;
+ }
+
+ public DynamicType getAssumeType() {
+ return assumeType;
+ }
+
+ public AbstractValue getAssumeValue() {
+ return assumeValue;
+ }
+
+ public boolean isEmpty() {
+ if (this == empty()) {
+ return true;
}
+ assert !assumeType.isUnknown() || !assumeValue.isUnknown() || isSideEffectFree;
+ return false;
}
- private final AssumeType type;
- private final ProguardMemberRule rule;
-
- public AssumeInfo(AssumeType type, ProguardMemberRule rule) {
- this.type = type;
- this.rule = rule;
+ public boolean isSideEffectFree() {
+ return isSideEffectFree;
}
- public boolean hasReturnInfo() {
- return rule.hasReturnValue();
+ public AssumeInfo meet(AssumeInfo other) {
+ DynamicType meetType = internalMeetType(assumeType, other.assumeType);
+ AbstractValue meetValue = internalMeetValue(assumeValue, other.assumeValue);
+ boolean meetIsSideEffectFree =
+ internalMeetIsSideEffectFree(isSideEffectFree, other.isSideEffectFree);
+ return AssumeInfo.create(meetType, meetValue, meetIsSideEffectFree);
}
- public ProguardMemberRuleReturnValue getReturnInfo() {
- return rule.getReturnValue();
+ private static DynamicType internalMeetType(DynamicType type, DynamicType other) {
+ if (type.equals(other)) {
+ return type;
+ }
+ if (type.isUnknown()) {
+ return other;
+ }
+ if (other.isUnknown()) {
+ return type;
+ }
+ return DynamicType.unknown();
}
- public boolean isAssumeNoSideEffects() {
- return type == AssumeType.ASSUME_NO_SIDE_EFFECTS;
+ private static AbstractValue internalMeetValue(AbstractValue value, AbstractValue other) {
+ if (value.equals(other)) {
+ return value;
+ }
+ if (value.isUnknown()) {
+ return other;
+ }
+ if (other.isUnknown()) {
+ return value;
+ }
+ return AbstractValue.unknown();
}
- public boolean isAssumeValues() {
- return type == AssumeType.ASSUME_VALUES;
+ private static boolean internalMeetIsSideEffectFree(
+ boolean isSideEffectFree, boolean otherIsSideEffectFree) {
+ return isSideEffectFree || otherIsSideEffectFree;
}
- public AssumeInfo meet(AssumeInfo lookup) {
- return new AssumeInfo(type.meet(lookup.type), rule.hasReturnValue() ? rule : lookup.rule);
+ public AssumeInfo rewrittenWithLens(AppView<?> appView, GraphLens graphLens) {
+ // Verify that there is no need to rewrite the assumed type.
+ assert assumeType.isNotNullType() || assumeType.isUnknown();
+ // If the assumed value is a static field, then rewrite it.
+ if (assumeValue.isSingleFieldValue()) {
+ DexField field = assumeValue.asSingleFieldValue().getField();
+ DexField rewrittenField = graphLens.getRenamedFieldSignature(field);
+ if (rewrittenField != field) {
+ SingleFieldValue rewrittenAssumeValue =
+ appView
+ .abstractValueFactory()
+ .createSingleFieldValue(rewrittenField, ObjectState.empty());
+ return create(assumeType, rewrittenAssumeValue, isSideEffectFree);
+ }
+ }
+ return this;
+ }
+
+ public AssumeInfo withoutPrunedItems(PrunedItems prunedItems) {
+ // Verify that there is no need to prune the assumed type.
+ assert assumeType.isNotNullType() || assumeType.isUnknown();
+ // If the assumed value is a static field, and the static field is removed, then prune the
+ // assumed value.
+ if (assumeValue.isSingleFieldValue()
+ && prunedItems.isRemoved(assumeValue.asSingleFieldValue().getField())) {
+ return create(assumeType, AbstractValue.unknown(), isSideEffectFree);
+ }
+ return this;
}
@Override
public boolean equals(Object other) {
- if (other == null) {
- return false;
+ if (this == other) {
+ return true;
}
- if (!(other instanceof AssumeInfo)) {
+ if (other == null || getClass() != other.getClass()) {
return false;
}
AssumeInfo assumeInfo = (AssumeInfo) other;
- return type == assumeInfo.type && rule == assumeInfo.rule;
+ return assumeValue.equals(assumeInfo.assumeValue)
+ && assumeType.equals(assumeInfo.assumeType)
+ && isSideEffectFree == assumeInfo.isSideEffectFree;
}
@Override
public int hashCode() {
- return type.ordinal() * 31 + rule.hashCode();
+ return Objects.hash(assumeValue, assumeType, isSideEffectFree);
+ }
+
+ public static class Builder {
+
+ private DynamicType assumeType = DynamicType.unknown();
+ private AbstractValue assumeValue = AbstractValue.unknown();
+ private boolean isSideEffectFree = false;
+
+ public Builder meet(AssumeInfo assumeInfo) {
+ return meetAssumeType(assumeInfo.assumeType)
+ .meetAssumeValue(assumeInfo.assumeValue)
+ .meetIsSideEffectFree(assumeInfo.isSideEffectFree);
+ }
+
+ public Builder meetAssumeType(DynamicType assumeType) {
+ this.assumeType = internalMeetType(this.assumeType, assumeType);
+ return this;
+ }
+
+ public Builder meetAssumeValue(AbstractValue assumeValue) {
+ this.assumeValue = internalMeetValue(this.assumeValue, assumeValue);
+ return this;
+ }
+
+ public Builder meetIsSideEffectFree(boolean isSideEffectFree) {
+ this.isSideEffectFree = internalMeetIsSideEffectFree(this.isSideEffectFree, isSideEffectFree);
+ return this;
+ }
+
+ public Builder setIsSideEffectFree() {
+ this.isSideEffectFree = true;
+ return this;
+ }
+
+ public AssumeInfo build() {
+ return create(assumeType, assumeValue, isSideEffectFree);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
index d207ac7..223fa00 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/membervaluepropagation/assume/AssumeInfoLookup.java
@@ -4,15 +4,11 @@
package com.android.tools.r8.ir.optimize.membervaluepropagation.assume;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClassAndMember;
import com.android.tools.r8.graph.DexClassAndMethod;
-import com.android.tools.r8.graph.DexMember;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo.AssumeType;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.android.tools.r8.shaking.AssumeInfoCollection;
public class AssumeInfoLookup {
@@ -20,33 +16,12 @@
AppView<AppInfoWithLiveness> appView,
SingleResolutionResult<?> resolutionResult,
DexClassAndMethod singleTarget) {
- AssumeInfo resolutionLookup = lookupAssumeInfo(appView, resolutionResult.getResolutionPair());
- if (resolutionLookup == null) {
- return singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
- }
+ AssumeInfoCollection assumeInfoCollection = appView.getAssumeInfoCollection();
+ AssumeInfo resolutionLookup = assumeInfoCollection.get(resolutionResult.getResolutionPair());
AssumeInfo singleTargetLookup =
- singleTarget != null ? lookupAssumeInfo(appView, singleTarget) : null;
+ singleTarget != null ? assumeInfoCollection.get(singleTarget) : null;
return singleTargetLookup != null
? resolutionLookup.meet(singleTargetLookup)
: resolutionLookup;
}
-
- public static AssumeInfo lookupAssumeInfo(
- AppView<? extends AppInfoWithClassHierarchy> appView, DexClassAndMember<?, ?> member) {
- DexMember<?, ?> reference = member.getReference();
- ProguardMemberRule assumeNoSideEffectsRule = appView.rootSet().noSideEffects.get(reference);
- ProguardMemberRule assumeValuesRule = appView.rootSet().assumedValues.get(reference);
- if (assumeNoSideEffectsRule == null && assumeValuesRule == null) {
- return null;
- }
- AssumeType type =
- assumeNoSideEffectsRule != null
- ? AssumeType.ASSUME_NO_SIDE_EFFECTS
- : AssumeType.ASSUME_VALUES;
- if ((assumeNoSideEffectsRule != null && assumeNoSideEffectsRule.hasReturnValue())
- || assumeValuesRule == null) {
- return new AssumeInfo(type, assumeNoSideEffectsRule);
- }
- return new AssumeInfo(type, assumeValuesRule);
- }
}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 418a665..cdb9762 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.naming.NamingLens;
@@ -242,8 +243,15 @@
clazz.accessFlags.setSuper();
}
}
+ boolean allowInvalidCfAccessFlags = false;
+ if (clazz
+ .getType()
+ .getDescriptor()
+ .endsWith(appView.dexItemFactory().createString("/package-info;"))) {
+ allowInvalidCfAccessFlags = true;
+ }
int access =
- options.testing.allowInvalidCfAccessFlags
+ allowInvalidCfAccessFlags || options.testing.allowInvalidCfAccessFlags
? clazz.accessFlags.materialize()
: clazz.accessFlags.getAsCfAccessFlags();
if (clazz.isDeprecated()) {
@@ -280,6 +288,10 @@
: "A nest host cannot also be a nest member.";
}
+ for (PermittedSubclassAttribute entry : clazz.getPermittedSubclassAttributes()) {
+ entry.write(writer, getNamingLens());
+ }
+
if (clazz.isRecord()) {
// TODO(b/169645628): Strip record components if not kept.
for (DexEncodedField instanceField : clazz.instanceFields()) {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 0b0d20f..9f01beb 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -47,19 +47,20 @@
private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
private LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
+ private final Map<String, String> originalSourceFiles = new HashMap<>();
@Override
public ClassNamingForNameMapper.Builder classNamingBuilder(
String renamedName, String originalName, Position position) {
ClassNamingForNameMapper.Builder classNamingBuilder =
- ClassNamingForNameMapper.builder(renamedName, originalName);
+ ClassNamingForNameMapper.builder(renamedName, originalName, originalSourceFiles::put);
mapping.put(renamedName, classNamingBuilder);
return classNamingBuilder;
}
@Override
public ClassNameMapper build() {
- return new ClassNameMapper(buildClassNameMappings(), mapVersions);
+ return new ClassNameMapper(buildClassNameMappings(), mapVersions, originalSourceFiles);
}
private ImmutableMap<String, ClassNamingForNameMapper> buildClassNameMappings() {
@@ -159,8 +160,7 @@
LineReader reader,
DiagnosticsHandler diagnosticsHandler,
boolean allowEmptyMappedRanges,
- boolean allowExperimentalMapping,
- Set<String> buildForClass)
+ boolean allowExperimentalMapping)
throws IOException {
try (ProguardMapReader proguardReader =
new ProguardMapReader(
@@ -178,12 +178,15 @@
private BiMapContainer<String, String> nameMapping;
private final Map<Signature, Signature> signatureMap = new HashMap<>();
private final Set<MapVersionMappingInformation> mapVersions;
+ private final Map<String, String> originalSourceFiles;
private ClassNameMapper(
ImmutableMap<String, ClassNamingForNameMapper> classNameMappings,
- Set<MapVersionMappingInformation> mapVersions) {
+ Set<MapVersionMappingInformation> mapVersions,
+ Map<String, String> originalSourceFiles) {
this.classNameMappings = classNameMappings;
this.mapVersions = mapVersions;
+ this.originalSourceFiles = originalSourceFiles;
}
public Map<String, ClassNamingForNameMapper> getClassNameMappings() {
@@ -235,6 +238,10 @@
return descriptorToJavaType(asString, this);
}
+ public String getSourceFile(String typeName) {
+ return originalSourceFiles.get(typeName);
+ }
+
@Override
public boolean hasMapping(DexType type) {
String decoded = descriptorToJavaType(type.descriptor.toString());
@@ -259,7 +266,7 @@
ImmutableMap.Builder<String, ClassNamingForNameMapper> builder = ImmutableMap.builder();
builder.orderEntriesByValue(Comparator.comparing(x -> x.originalName));
classNameMappings.forEach(builder::put);
- return new ClassNameMapper(builder.build(), mapVersions);
+ return new ClassNameMapper(builder.build(), mapVersions, originalSourceFiles);
}
public boolean verifyIsSorted() {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 373f584..c1e5de7 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -23,6 +23,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -43,10 +44,15 @@
private final Map<String, List<MappedRange>> mappedRangesByName = Maps.newHashMap();
private final Map<String, List<MemberNaming>> mappedFieldNamingsByName = Maps.newHashMap();
private final List<MappingInformation> additionalMappingInfo = new ArrayList<>();
+ private final BiConsumer<String, String> originalSourceFileConsumer;
- private Builder(String renamedName, String originalName) {
+ private Builder(
+ String renamedName,
+ String originalName,
+ BiConsumer<String, String> originalSourceFileConsumer) {
this.originalName = originalName;
this.renamedName = renamedName;
+ this.originalSourceFileConsumer = originalSourceFileConsumer;
}
@Override
@@ -108,6 +114,9 @@
}
}
additionalMappingInfo.add(info);
+ if (info.isFileNameInformation()) {
+ originalSourceFileConsumer.accept(originalName, info.asFileNameInformation().getFileName());
+ }
}
}
@@ -205,8 +214,11 @@
}
}
- static Builder builder(String renamedName, String originalName) {
- return new Builder(renamedName, originalName);
+ static Builder builder(
+ String renamedName,
+ String originalName,
+ BiConsumer<String, String> originalSourceFileConsumer) {
+ return new Builder(renamedName, originalName, originalSourceFileConsumer);
}
public final String originalName;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 0f67814..2efdb3c 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -103,10 +103,11 @@
currentResolutionResult.withInitialResolutionHolder(
currentResolutionResult.getResolvedHolder()),
contexts,
- invokeType)) {
+ invokeType,
+ original)) {
eligibleLibraryMethod = currentResolvedMethod.asLibraryMethod();
}
- if (appView.appInfo().isAssumeMethod(currentResolvedMethod)) {
+ if (appView.getAssumeInfoCollection().contains(currentResolvedMethod)) {
break;
}
DexClass currentResolvedHolder = currentResolvedMethod.getHolder();
@@ -140,7 +141,8 @@
DexClassAndMethod resolvedMethod,
SingleResolutionResult<?> resolutionResult,
ProgramMethodSet contexts,
- Type invokeType) {
+ Type invokeType,
+ DexMethod original) {
// TODO(b/194422791): It could potentially be that `original.holder` is not a subtype of
// `original.holder` on all API levels, in which case it is not OK to rebind to the resolved
// method.
@@ -149,7 +151,7 @@
&& !isInvokeSuperToInterfaceMethod(resolvedMethod, invokeType)
&& !isInvokeSuperToAbstractMethod(resolvedMethod, invokeType)
&& isApiSafeForMemberRebinding(
- resolvedMethod.asLibraryMethod(), androidApiLevelCompute, options);
+ resolvedMethod.asLibraryMethod(), original, androidApiLevelCompute, options);
}
private boolean isAccessibleInAllContexts(
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
index 5c9e671..b381e18 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/BottomCfFrameState.java
@@ -73,6 +73,11 @@
}
@Override
+ public ErroneousCfFrameState popArray(AppView<?> appView) {
+ return pop();
+ }
+
+ @Override
public ErroneousCfFrameState popInitialized(
AppView<?> appView,
DexType expectedType,
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
index 2131f02..dfa38f5 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/CfFrameState.java
@@ -44,22 +44,21 @@
public static ErroneousCfFrameState errorUnexpectedStack(
FrameType frameType, DexType expectedType) {
- return internalErrorUnexpectedStack(formatActual(frameType), formatExpected(expectedType));
+ return errorUnexpectedStack(frameType, formatExpected(expectedType));
}
public static ErroneousCfFrameState errorUnexpectedStack(
FrameType frameType, FrameType expectedType) {
- return internalErrorUnexpectedStack(formatActual(frameType), formatExpected(expectedType));
+ return errorUnexpectedStack(frameType, formatExpected(expectedType));
}
public static ErroneousCfFrameState errorUnexpectedStack(
FrameType frameType, ValueType expectedType) {
- return internalErrorUnexpectedStack(formatActual(frameType), formatExpected(expectedType));
+ return errorUnexpectedStack(frameType, formatExpected(expectedType));
}
- private static ErroneousCfFrameState internalErrorUnexpectedStack(
- String actual, String expected) {
- return internalError(actual, expected, "on stack");
+ public static ErroneousCfFrameState errorUnexpectedStack(FrameType frameType, String expected) {
+ return internalError(formatActual(frameType), expected, "on stack");
}
private static ErroneousCfFrameState internalError(
@@ -119,6 +118,8 @@
public abstract CfFrameState popAndInitialize(
AppView<?> appView, DexMethod constructor, CfAnalysisConfig config);
+ public abstract CfFrameState popArray(AppView<?> appView);
+
public final CfFrameState popInitialized(AppView<?> appView, DexType expectedType) {
return popInitialized(appView, expectedType, FunctionUtils::getFirst);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
index e920d8e..f05ab67 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ConcreteCfFrameState.java
@@ -192,6 +192,21 @@
}
@Override
+ public CfFrameState popArray(AppView<?> appView) {
+ return pop(
+ (state, head) ->
+ isArrayTypeOrNull(head) ? state : errorUnexpectedStack(head, "an array type"));
+ }
+
+ private static boolean isArrayTypeOrNull(FrameType frameType) {
+ if (frameType.isInitializedReferenceType()
+ && frameType.asInitializedReferenceType().getInitializedType().isArrayType()) {
+ return true;
+ }
+ return frameType.isNullType();
+ }
+
+ @Override
public CfFrameState popInitialized(
AppView<?> appView,
DexType expectedType,
diff --git a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
index 5f8569b..83cd59d 100644
--- a/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
+++ b/src/main/java/com/android/tools/r8/optimize/interfaces/analysis/ErroneousCfFrameState.java
@@ -156,6 +156,11 @@
}
@Override
+ public CfFrameState popArray(AppView<?> appView) {
+ return this;
+ }
+
+ @Override
public CfFrameState popInitialized(
AppView<?> appView,
DexType expectedType,
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
index 1ff65a4..13187cc 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
@@ -12,5 +12,7 @@
abstract ClassNamingForNameMapper getClassNaming(String typeName);
+ abstract String getSourceFileForClass(String typeName);
+
abstract Set<MapVersionMappingInformation> getMapVersions();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
index a5c1dd6..ebba380 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
@@ -73,7 +73,7 @@
proguardMapProducer.get(), buildForClass);
return new ProguardMappingProviderImpl(
ClassNameMapper.mapperFromLineReaderWithFiltering(
- reader, diagnosticsHandler, true, allowExperimental, buildForClass));
+ reader, diagnosticsHandler, true, allowExperimental));
} catch (Exception e) {
throw new InvalidMappingFileException(e);
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
index dcfe2cb..c15efef 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
@@ -43,4 +43,9 @@
}
return classNameMapper.getClassNaming(typeName);
}
+
+ @Override
+ String getSourceFileForClass(String typeName) {
+ return classNameMapper.getSourceFile(typeName);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
index 7f71ad6..80931c3 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceClassResultImpl.java
@@ -201,16 +201,7 @@
@Override
public RetracedSourceFile getSourceFile() {
- String sourceFile = null;
- if (mapper != null) {
- for (MappingInformation info : mapper.getAdditionalMappingInfo()) {
- if (info.isFileNameInformation()) {
- sourceFile = info.asFileNameInformation().getFileName();
- break;
- }
- }
- }
- return new RetracedSourceFileImpl(getRetracedClass().getClassReference(), sourceFile);
+ return RetraceUtils.getSourceFile(classReference, classResult.retracer);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
index 0aa210c..8243d4a 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceFrameResultImpl.java
@@ -221,7 +221,7 @@
@Override
public boolean isEmpty() {
- return !mappedRanges.isEmpty();
+ return mappedRanges.isEmpty();
}
public static class ElementImpl implements RetraceFrameElement {
@@ -348,8 +348,7 @@
@Override
public RetracedSourceFile getSourceFile(RetracedClassMemberReference frame) {
- return RetraceUtils.getSourceFileOrLookup(
- frame.getHolderClass(), classElement, retraceFrameResult.retracer);
+ return RetraceUtils.getSourceFile(frame.getHolderClass(), retraceFrameResult.retracer);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
index e5a2579..af8526c 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceMethodResultImpl.java
@@ -197,8 +197,8 @@
@Override
public RetracedSourceFile getSourceFile() {
- return RetraceUtils.getSourceFileOrLookup(
- methodReference.getHolderClass(), classElement, retraceMethodResult.retracer);
+ return RetraceUtils.getSourceFile(
+ methodReference.getHolderClass(), retraceMethodResult.retracer);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index c4ed860..2e1411a 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -12,14 +12,10 @@
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.RetraceClassElement;
-import com.android.tools.r8.retrace.RetraceClassResult;
import com.android.tools.r8.retrace.RetracedClassReference;
import com.android.tools.r8.retrace.RetracedMethodReference;
import com.android.tools.r8.retrace.RetracedMethodReference.KnownRetracedMethodReference;
import com.android.tools.r8.retrace.RetracedSourceFile;
-import com.android.tools.r8.retrace.Retracer;
-import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
@@ -75,16 +71,10 @@
return clazz.substring(lastIndexOfPeriod + 1, endIndex);
}
- // TODO(b/226885646): Retracing of a source file should not be dependent on retraced information.
- public static RetracedSourceFile getSourceFileOrLookup(
- RetracedClassReference holder, RetraceClassElement context, Retracer retracer) {
- if (holder.equals(context.getRetracedClass())) {
- return context.getSourceFile();
- }
- RetraceClassResult contextClassResult = retracer.retraceClass(holder.getClassReference());
- Box<RetracedSourceFile> retraceSourceFile = new Box<>();
- contextClassResult.forEach(element -> retraceSourceFile.set(element.getSourceFile()));
- return retraceSourceFile.get();
+ public static RetracedSourceFile getSourceFile(
+ RetracedClassReference holder, RetracerImpl retracer) {
+ ClassReference holderReference = holder.getClassReference();
+ return new RetracedSourceFileImpl(holderReference, retracer.getSourceFile(holderReference));
}
public static String inferSourceFile(
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index 92e42f1..346b417 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -87,6 +87,10 @@
return classNameMapperProvider.getMapVersions();
}
+ public String getSourceFile(ClassReference classReference) {
+ return classNameMapperProvider.getSourceFileForClass(classReference.getTypeName());
+ }
+
public static Builder builder() {
return new Builder();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
index 987aead..a8b5e71 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/StackTraceElementProxyRetracerImpl.java
@@ -131,7 +131,7 @@
? OptionalInt.of(element.getLineNumber())
: OptionalInt.empty(),
element.getMethodName());
- if (!frameResult.isEmpty()) {
+ if (frameResult.isEmpty()) {
return classResult.stream()
.map(
classElement ->
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 94b44d0..677338b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMember;
-import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexDefinitionSupplier;
@@ -143,10 +142,6 @@
private final KeepInfoCollection keepInfo;
/** All items with assumemayhavesideeffects rule. */
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
- /** All items with assumenosideeffects rule. */
- public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
- /** All items with assumevalues rule. */
- public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
/** All methods that should be inlined if possible due to a configuration directive. */
private final Set<DexMethod> alwaysInline;
/**
@@ -222,8 +217,6 @@
Map<DexCallSite, ProgramMethodSet> callSites,
KeepInfoCollection keepInfo,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
Set<DexMethod> alwaysInline,
Set<DexMethod> neverInlineDueToSingleCaller,
Set<DexMethod> whyAreYouNotInlining,
@@ -255,8 +248,6 @@
this.objectAllocationInfoCollection = objectAllocationInfoCollection;
this.keepInfo = keepInfo;
this.mayHaveSideEffects = mayHaveSideEffects;
- this.noSideEffects = noSideEffects;
- this.assumedValues = assumedValues;
this.callSites = callSites;
this.alwaysInline = alwaysInline;
this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
@@ -299,8 +290,6 @@
previous.callSites,
previous.keepInfo,
previous.mayHaveSideEffects,
- previous.noSideEffects,
- previous.assumedValues,
previous.alwaysInline,
previous.neverInlineDueToSingleCaller,
previous.whyAreYouNotInlining,
@@ -346,8 +335,6 @@
previous.callSites,
extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()),
previous.mayHaveSideEffects,
- pruneMapFromMembers(previous.noSideEffects, prunedItems, executorService, futures),
- pruneMapFromMembers(previous.assumedValues, prunedItems, executorService, futures),
pruneMethods(previous.alwaysInline, prunedItems, executorService, futures),
pruneMethods(previous.neverInlineDueToSingleCaller, prunedItems, executorService, futures),
pruneMethods(previous.whyAreYouNotInlining, prunedItems, executorService, futures),
@@ -551,8 +538,6 @@
callSites,
keepInfo,
mayHaveSideEffects,
- noSideEffects,
- assumedValues,
alwaysInline,
neverInlineDueToSingleCaller,
whyAreYouNotInlining,
@@ -630,8 +615,6 @@
this.objectAllocationInfoCollection = previous.objectAllocationInfoCollection;
this.keepInfo = previous.keepInfo;
this.mayHaveSideEffects = previous.mayHaveSideEffects;
- this.noSideEffects = previous.noSideEffects;
- this.assumedValues = previous.assumedValues;
this.callSites = previous.callSites;
this.alwaysInline = previous.alwaysInline;
this.neverInlineDueToSingleCaller = previous.neverInlineDueToSingleCaller;
@@ -762,26 +745,6 @@
return neverInlineDueToSingleCaller.contains(method.getReference());
}
- public boolean isAssumeMethod(DexClassAndMethod method) {
- return isAssumeNoSideEffectsMethod(method) || isAssumeValuesMethod(method);
- }
-
- public boolean isAssumeNoSideEffectsMethod(DexMethod method) {
- return noSideEffects.containsKey(method);
- }
-
- public boolean isAssumeNoSideEffectsMethod(DexClassAndMethod method) {
- return isAssumeNoSideEffectsMethod(method.getReference());
- }
-
- public boolean isAssumeValuesMethod(DexMethod method) {
- return assumedValues.containsKey(method);
- }
-
- public boolean isAssumeValuesMethod(DexClassAndMethod method) {
- return isAssumeValuesMethod(method.getReference());
- }
-
public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
return whyAreYouNotInlining.contains(method);
}
@@ -950,9 +913,8 @@
assert info.isRead() || info.isWritten();
return true;
}
- // TODO(b/192924387): When we enqueue a field as a root item, we should maybe create a
- // FieldAccessInfo that describes the field is read and written using reflection.
- return !getKeepInfo().getFieldInfo(reference, this).isShrinkingAllowed(options());
+ assert getKeepInfo().getFieldInfo(reference, this).isShrinkingAllowed(options());
+ return false;
}
public boolean isFieldRead(DexEncodedField encodedField) {
@@ -1240,13 +1202,6 @@
keepInfo.rewrite(definitionSupplier, lens, application.options),
// Take any rule in case of collisions.
lens.rewriteReferenceKeys(mayHaveSideEffects, (reference, rules) -> ListUtils.first(rules)),
- // Take the assume rule from the representative in case of collisions.
- lens.rewriteReferenceKeys(
- noSideEffects,
- (reference, rules) -> noSideEffects.get(lens.getOriginalMemberSignature(reference))),
- lens.rewriteReferenceKeys(
- assumedValues,
- (reference, rules) -> assumedValues.get(lens.getOriginalMemberSignature(reference))),
lens.rewriteReferences(alwaysInline),
lens.rewriteReferences(neverInlineDueToSingleCaller),
lens.rewriteReferences(whyAreYouNotInlining),
diff --git a/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java
new file mode 100644
index 0000000..efff258
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/AssumeInfoCollection.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexMember;
+import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
+import com.android.tools.r8.utils.MapUtils;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+public class AssumeInfoCollection {
+
+ private final Map<DexMember<?, ?>, AssumeInfo> backing;
+
+ AssumeInfoCollection(Map<DexMember<?, ?>, AssumeInfo> backing) {
+ assert backing.values().stream().noneMatch(AssumeInfo::isEmpty);
+ this.backing = backing;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public boolean contains(DexClassAndMember<?, ?> member) {
+ return backing.containsKey(member.getReference());
+ }
+
+ public AssumeInfo get(DexMember<?, ?> member) {
+ return backing.getOrDefault(member, AssumeInfo.empty());
+ }
+
+ public AssumeInfo get(DexClassAndMember<?, ?> member) {
+ return get(member.getReference());
+ }
+
+ public boolean isSideEffectFree(DexMember<?, ?> member) {
+ return get(member).isSideEffectFree();
+ }
+
+ public boolean isSideEffectFree(DexClassAndMember<?, ?> member) {
+ return isSideEffectFree(member.getReference());
+ }
+
+ public AssumeInfoCollection rewrittenWithLens(AppView<?> appView, GraphLens graphLens) {
+ Map<DexMember<?, ?>, AssumeInfo> rewrittenCollection = new IdentityHashMap<>();
+ backing.forEach(
+ (reference, info) -> {
+ DexMember<?, ?> rewrittenReference = graphLens.getRenamedMemberSignature(reference);
+ AssumeInfo rewrittenInfo = info.rewrittenWithLens(appView, graphLens);
+ assert !rewrittenInfo.isEmpty();
+ rewrittenCollection.put(rewrittenReference, rewrittenInfo);
+ });
+ return new AssumeInfoCollection(rewrittenCollection);
+ }
+
+ public AssumeInfoCollection withoutPrunedItems(PrunedItems prunedItems) {
+ Map<DexMember<?, ?>, AssumeInfo> rewrittenCollection = new IdentityHashMap<>();
+ backing.forEach(
+ (reference, info) -> {
+ if (!prunedItems.isRemoved(reference)) {
+ AssumeInfo rewrittenInfo = info.withoutPrunedItems(prunedItems);
+ if (!rewrittenInfo.isEmpty()) {
+ rewrittenCollection.put(reference, rewrittenInfo);
+ }
+ }
+ });
+ return new AssumeInfoCollection(rewrittenCollection);
+ }
+
+ public static class Builder {
+
+ private final Map<DexMember<?, ?>, AssumeInfo.Builder> backing = new ConcurrentHashMap<>();
+
+ public Builder applyIf(boolean condition, Consumer<Builder> consumer) {
+ if (condition) {
+ consumer.accept(this);
+ }
+ return this;
+ }
+
+ public AssumeInfo buildInfo(DexClassAndMember<?, ?> member) {
+ AssumeInfo.Builder builder = backing.get(member.getReference());
+ return builder != null ? builder.build() : AssumeInfo.empty();
+ }
+
+ private AssumeInfo.Builder getOrCreateAssumeInfo(DexMember<?, ?> member) {
+ return backing.computeIfAbsent(member, ignoreKey(AssumeInfo::builder));
+ }
+
+ private AssumeInfo.Builder getOrCreateAssumeInfo(DexClassAndMember<?, ?> member) {
+ return getOrCreateAssumeInfo(member.getReference());
+ }
+
+ public boolean isEmpty() {
+ return backing.isEmpty();
+ }
+
+ public Builder meet(DexMember<?, ?> member, AssumeInfo assumeInfo) {
+ getOrCreateAssumeInfo(member).meet(assumeInfo);
+ return this;
+ }
+
+ public Builder meetAssumeType(DexClassAndMember<?, ?> member, DynamicType assumeType) {
+ getOrCreateAssumeInfo(member).meetAssumeType(assumeType);
+ return this;
+ }
+
+ public Builder meetAssumeValue(DexMember<?, ?> member, AbstractValue assumeValue) {
+ getOrCreateAssumeInfo(member).meetAssumeValue(assumeValue);
+ return this;
+ }
+
+ public Builder meetAssumeValue(DexClassAndMember<?, ?> member, AbstractValue assumeValue) {
+ return meetAssumeValue(member.getReference(), assumeValue);
+ }
+
+ public Builder setIsSideEffectFree(DexMember<?, ?> member) {
+ getOrCreateAssumeInfo(member).setIsSideEffectFree();
+ return this;
+ }
+
+ public Builder setIsSideEffectFree(DexClassAndMember<?, ?> member) {
+ return setIsSideEffectFree(member.getReference());
+ }
+
+ public AssumeInfoCollection build() {
+ return new AssumeInfoCollection(
+ MapUtils.newIdentityHashMap(
+ builder ->
+ backing.forEach(
+ (reference, infoBuilder) -> {
+ AssumeInfo info = infoBuilder.build();
+ if (!info.isEmpty()) {
+ builder.accept(reference, info);
+ }
+ }),
+ backing.size()));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/CheckEnumUnboxedRule.java b/src/main/java/com/android/tools/r8/shaking/CheckEnumUnboxedRule.java
new file mode 100644
index 0000000..da2fbbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/CheckEnumUnboxedRule.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class CheckEnumUnboxedRule extends ProguardConfigurationRule {
+
+ public static final String RULE_NAME = "checkenumunboxed";
+
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<CheckEnumUnboxedRule, Builder> {
+
+ private Builder() {
+ super();
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public CheckEnumUnboxedRule build() {
+ return new CheckEnumUnboxedRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+ }
+
+ protected CheckEnumUnboxedRule(
+ Origin origin,
+ Position position,
+ String source,
+ List<ProguardTypeMatcher> classAnnotations,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules) {
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ String typeString() {
+ return RULE_NAME;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ConvertCheckNotNullRule.java b/src/main/java/com/android/tools/r8/shaking/ConvertCheckNotNullRule.java
new file mode 100644
index 0000000..dd372e3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ConvertCheckNotNullRule.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ConvertCheckNotNullRule extends ProguardConfigurationRule {
+
+ public static final String RULE_NAME = "convertchecknotnull";
+
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<ConvertCheckNotNullRule, Builder> {
+
+ private Builder() {
+ super();
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public ConvertCheckNotNullRule build() {
+ return new ConvertCheckNotNullRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+ }
+
+ private ConvertCheckNotNullRule(
+ Origin origin,
+ Position position,
+ String source,
+ List<ProguardTypeMatcher> classAnnotations,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules) {
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+
+ /** Create a new empty builder. */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ public boolean applyToNonProgramClasses() {
+ return true;
+ }
+
+ @Override
+ String typeString() {
+ return RULE_NAME;
+ }
+}
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 f7d3e74..3addb2d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.code.CfOrDexInstruction;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
@@ -1045,6 +1046,19 @@
}
}
+ private FieldAccessInfoImpl getOrCreateFieldAccessInfo(DexEncodedField field) {
+ // Check if we have previously created a FieldAccessInfo object for the field definition.
+ FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field.getReference());
+
+ // If not, we must create one.
+ if (info == null) {
+ info = new FieldAccessInfoImpl(field.getReference());
+ fieldAccessInfoCollection.extend(field.getReference(), info);
+ }
+
+ return info;
+ }
+
private boolean registerFieldAccess(
DexField field, ProgramMethod context, boolean isRead, boolean isReflective) {
FieldAccessInfoImpl info = fieldAccessInfoCollection.get(field);
@@ -1058,14 +1072,7 @@
return true;
}
- // Check if we have previously created a FieldAccessInfo object for the field definition.
- info = fieldAccessInfoCollection.get(encodedField.getReference());
-
- // If not, we must create one.
- if (info == null) {
- info = new FieldAccessInfoImpl(encodedField.getReference());
- fieldAccessInfoCollection.extend(encodedField.getReference(), info);
- }
+ info = getOrCreateFieldAccessInfo(encodedField);
// If `field` is an indirect reference, then create a mapping for it, such that we don't have
// to resolve the field the next time we see the reference.
@@ -2028,6 +2035,11 @@
assert !appView.unboxedEnums().isUnboxedEnum(clazz);
+ if (options.isGeneratingClassFiles() && clazz.hasPermittedSubclassAttributes()) {
+ throw new CompilationError(
+ "Sealed classes are not supported as program classes when generating class files",
+ clazz.getOrigin());
+ }
// Mark types in inner-class attributes referenced.
{
BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer =
@@ -4137,8 +4149,6 @@
callSites,
keepInfo,
rootSet.mayHaveSideEffects,
- rootSet.noSideEffects,
- rootSet.assumedValues,
amendWithCompanionMethods(rootSet.alwaysInline),
amendWithCompanionMethods(rootSet.neverInlineDueToSingleCaller),
amendWithCompanionMethods(rootSet.whyAreYouNotInlining),
@@ -4553,6 +4563,10 @@
// Package protected due to entry point from worklist.
void markFieldAsKept(ProgramField field, KeepReason reason) {
+ FieldAccessInfoImpl fieldAccessInfo = getOrCreateFieldAccessInfo(field.getDefinition());
+ fieldAccessInfo.setHasReflectiveRead();
+ fieldAccessInfo.setHasReflectiveWrite();
+
if (field.getDefinition().isStatic()) {
markFieldAsLive(field, field, reason);
} else {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
index 85d14b9..41c48cb 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracingImpl.java
@@ -26,7 +26,6 @@
import com.android.tools.r8.ir.conversion.IRToDexFinalizer;
import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
-import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfoLookup;
import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata;
import com.android.tools.r8.shaking.Enqueuer.Mode;
@@ -99,8 +98,8 @@
// If the value of the field is not guaranteed to be the default value, even if it is never
// assigned, then give up.
// TODO(b/205810841): Allow this by handling this in the corresponding IR rewriter.
- AssumeInfo assumeInfo = AssumeInfoLookup.lookupAssumeInfo(appView, field);
- if (assumeInfo != null && assumeInfo.hasReturnInfo()) {
+ AssumeInfo assumeInfo = appView.getAssumeInfoCollection().get(field);
+ if (!assumeInfo.getAssumeValue().isUnknown()) {
return enqueueDeferredEnqueuerActions(field);
}
if (field.getAccessFlags().isStatic() && field.getDefinition().hasExplicitStaticValue()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
index a09efe6..b2f5f9c 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerWorklist.java
@@ -736,7 +736,7 @@
@Override
public void enqueueTraceReflectiveFieldAccessAction(ProgramField field, ProgramMethod context) {
FieldAccessInfo info = enqueuer.getFieldAccessInfoCollection().get(field.getReference());
- if (info == null || !info.hasReflectiveAccess()) {
+ if (info == null || !info.hasReflectiveRead() || !info.hasReflectiveWrite()) {
queue.add(new TraceReflectiveFieldAccessAction(field, context));
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index bf969b7..7199fcb 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -32,8 +32,11 @@
return bottom().joiner();
}
+ private final boolean checkEnumUnboxed;
+
private KeepClassInfo(Builder builder) {
super(builder);
+ this.checkEnumUnboxed = builder.isCheckEnumUnboxedEnabled();
}
@Override
@@ -41,6 +44,14 @@
return new Builder(this);
}
+ public boolean isCheckEnumUnboxedEnabled(GlobalKeepInfoConfiguration configuration) {
+ return internalIsCheckEnumUnboxedEnabled();
+ }
+
+ boolean internalIsCheckEnumUnboxedEnabled() {
+ return checkEnumUnboxed;
+ }
+
public Joiner joiner() {
assert !isTop();
return new Joiner(this);
@@ -87,12 +98,34 @@
public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> {
+ private boolean checkEnumUnboxed;
+
private Builder() {
super();
}
private Builder(KeepClassInfo original) {
super(original);
+ checkEnumUnboxed = original.internalIsCheckEnumUnboxedEnabled();
+ }
+
+ // Check enum unboxed.
+
+ public boolean isCheckEnumUnboxedEnabled() {
+ return checkEnumUnboxed;
+ }
+
+ public Builder setCheckEnumUnboxed(boolean checkEnumUnboxed) {
+ this.checkEnumUnboxed = checkEnumUnboxed;
+ return self();
+ }
+
+ public Builder setCheckEnumUnboxed() {
+ return setCheckEnumUnboxed(true);
+ }
+
+ public Builder unsetCheckEnumUnboxed() {
+ return setCheckEnumUnboxed(false);
}
@Override
@@ -116,9 +149,25 @@
}
@Override
+ boolean internalIsEqualTo(KeepClassInfo other) {
+ return super.internalIsEqualTo(other)
+ && isCheckEnumUnboxedEnabled() == other.internalIsCheckEnumUnboxedEnabled();
+ }
+
+ @Override
public KeepClassInfo doBuild() {
return new KeepClassInfo(this);
}
+
+ @Override
+ public Builder makeTop() {
+ return super.makeTop().unsetCheckEnumUnboxed();
+ }
+
+ @Override
+ public Builder makeBottom() {
+ return super.makeBottom().unsetCheckEnumUnboxed();
+ }
}
public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepClassInfo> {
@@ -127,6 +176,11 @@
super(info.builder());
}
+ public Joiner setCheckEnumUnboxed() {
+ builder.setCheckEnumUnboxed();
+ return self();
+ }
+
@Override
public Joiner asClassJoiner() {
return this;
@@ -135,7 +189,8 @@
@Override
public Joiner merge(Joiner joiner) {
// Should be extended to merge the fields of this class in case any are added.
- return super.merge(joiner);
+ return super.merge(joiner)
+ .applyIf(joiner.builder.isCheckEnumUnboxedEnabled(), Joiner::setCheckEnumUnboxed);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java b/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
index 7f9ff8c..9bd37a5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
@@ -18,21 +18,22 @@
private int flags = 0;
// Ordered list of flag names. Must be consistent with getPredicates.
- private static final List<String> NAMES = ImmutableList.of(
- "public",
- "private",
- "protected",
- "static",
- "final",
- "abstract",
- "volatile",
- "transient",
- "synchronized",
- "native",
- "strictfp",
- "synthetic",
- "bridge"
- );
+ private static final List<String> NAMES =
+ ImmutableList.of(
+ "public",
+ "private",
+ "protected",
+ "static",
+ "final",
+ "abstract",
+ "volatile",
+ "transient",
+ "synchronized",
+ "native",
+ "strictfp",
+ "synthetic",
+ "bridge",
+ "constructor");
// Get ordered list of flag predicates. Must be consistent with getNames.
private List<BooleanSupplier> getPredicates() {
@@ -49,7 +50,8 @@
this::isNative,
this::isStrict,
this::isSynthetic,
- this::isBridge);
+ this::isBridge,
+ this::isConstructor);
}
private boolean containsAll(int other) {
@@ -194,6 +196,14 @@
return isSet(Constants.ACC_BRIDGE);
}
+ public void setConstructor() {
+ set(Constants.ACC_CONSTRUCTOR);
+ }
+
+ public boolean isConstructor() {
+ return isSet(Constants.ACC_CONSTRUCTOR);
+ }
+
private boolean isSet(int flag) {
return (flags & flag) != 0;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index c1f5a95..36bd397 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -70,7 +70,7 @@
private boolean configurationDebugging = false;
private boolean dontUseMixedCaseClassnames = false;
private boolean protoShrinking = false;
- private int maxRemovedAndroidLogLevel = 1;
+ private int maxRemovedAndroidLogLevel = -1;
private Builder(DexItemFactory dexItemFactory, Reporter reporter) {
this.dexItemFactory = dexItemFactory;
@@ -294,12 +294,21 @@
protoShrinking = true;
}
- public int getMaxRemovedAndroidLogLevel() {
- return maxRemovedAndroidLogLevel;
+ public int getMaxRemovedAndroidLogLevelOrDefault(int defaultValue) {
+ assert maxRemovedAndroidLogLevel == -1 || maxRemovedAndroidLogLevel >= 1;
+ return maxRemovedAndroidLogLevel >= 1 ? maxRemovedAndroidLogLevel : defaultValue;
}
- public void setMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
- this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+ public void joinMaxRemovedAndroidLogLevel(int maxRemovedAndroidLogLevel) {
+ assert maxRemovedAndroidLogLevel >= 1;
+ if (this.maxRemovedAndroidLogLevel == -1) {
+ this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+ } else {
+ // If there are multiple -maximumremovedandroidloglevel rules we only allow removing logging
+ // calls that are removable according to all rules.
+ this.maxRemovedAndroidLogLevel =
+ Math.min(this.maxRemovedAndroidLogLevel, maxRemovedAndroidLogLevel);
+ }
}
public ProguardConfiguration buildRaw() {
@@ -344,7 +353,7 @@
configurationDebugging,
dontUseMixedCaseClassnames,
protoShrinking,
- maxRemovedAndroidLogLevel);
+ getMaxRemovedAndroidLogLevelOrDefault(1));
reporter.failIfPendingErrors();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index c4af05f..8b4210b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -8,10 +8,10 @@
import com.android.tools.r8.InputDependencyGraphConsumer;
import com.android.tools.r8.Version;
import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
@@ -19,7 +19,6 @@
import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
-import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
import com.android.tools.r8.utils.IdentifierUtils;
@@ -47,12 +46,10 @@
public class ProguardConfigurationParser {
private final Builder configurationBuilder;
-
private final DexItemFactory dexItemFactory;
-
+ private final ProguardConfigurationParserOptions options;
private final Reporter reporter;
private final InputDependencyGraphConsumer inputDependencyConsumer;
- private final boolean allowTestOptions;
public static final String FLATTEN_PACKAGE_HIERARCHY = "flattenpackagehierarchy";
public static final String REPACKAGE_CLASSES = "repackageclasses";
@@ -65,26 +62,25 @@
private static final List<String> IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS =
ImmutableList.of("runtype", "laststageoutput");
- private static final List<String> IGNORED_FLAG_OPTIONS = ImmutableList.of(
- "forceprocessing",
- "dontpreverify",
- "experimentalshrinkunusedprotofields",
- "filterlibraryjarswithorginalprogramjars",
- "dontskipnonpubliclibraryclasses",
- "dontskipnonpubliclibraryclassmembers",
- "invokebasemethod",
- // TODO(b/62524562): we may support this later.
- "mergeinterfacesaggressively",
- "android",
- "allowruntypeandignoreoptimizationpasses",
- "dontshrinkduringoptimization",
- "convert_proto_enum_to_string");
+ private static final List<String> IGNORED_FLAG_OPTIONS =
+ ImmutableList.of(
+ "forceprocessing",
+ "dontpreverify",
+ "experimentalshrinkunusedprotofields",
+ "filterlibraryjarswithorginalprogramjars",
+ "dontskipnonpubliclibraryclasses",
+ "dontskipnonpubliclibraryclassmembers",
+ "invokebasemethod",
+ // TODO(b/62524562): we may support this later.
+ "mergeinterfacesaggressively",
+ "android",
+ "allowruntypeandignoreoptimizationpasses",
+ "dontshrinkduringoptimization",
+ "convert_proto_enum_to_string",
+ "keepkotlinmetadata");
private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS =
- ImmutableList.of(
- "isclassnamestring", "whyarenotsimple", "convertchecknotnull", "checkenumunboxed");
-
- private static final List<String> IGNORED_RETURN_VALUE_ATTRIBUTES = ImmutableList.of("_NONNULL_");
+ ImmutableList.of("isclassnamestring", "whyarenotsimple");
private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList.of(
// TODO(b/37137994): -outjars should be reported as errors, not just as warnings!
@@ -122,23 +118,37 @@
public ProguardConfigurationParser(
DexItemFactory dexItemFactory, Reporter reporter) {
- this(dexItemFactory, reporter, null, false);
+ this(
+ dexItemFactory,
+ reporter,
+ ProguardConfigurationParserOptions.builder()
+ .setEnableExperimentalCheckEnumUnboxed(false)
+ .setEnableExperimentalConvertCheckNotNull(false)
+ .setEnableExperimentalWhyAreYouNotInlining(false)
+ .setEnableTestingOptions(false)
+ .build());
}
public ProguardConfigurationParser(
DexItemFactory dexItemFactory,
Reporter reporter,
- InputDependencyGraphConsumer inputDependencyConsumer,
- boolean allowTestOptions) {
- this.dexItemFactory = dexItemFactory;
- configurationBuilder = ProguardConfiguration.builder(dexItemFactory, reporter);
+ ProguardConfigurationParserOptions options) {
+ this(dexItemFactory, reporter, options, null);
+ }
+ public ProguardConfigurationParser(
+ DexItemFactory dexItemFactory,
+ Reporter reporter,
+ ProguardConfigurationParserOptions options,
+ InputDependencyGraphConsumer inputDependencyConsumer) {
+ this.configurationBuilder = ProguardConfiguration.builder(dexItemFactory, reporter);
+ this.dexItemFactory = dexItemFactory;
+ this.options = options;
this.reporter = reporter;
this.inputDependencyConsumer =
inputDependencyConsumer != null
? inputDependencyConsumer
: emptyInputDependencyGraphConsumer();
- this.allowTestOptions = allowTestOptions;
}
private static InputDependencyGraphConsumer emptyInputDependencyGraphConsumer() {
@@ -272,6 +282,7 @@
expectChar('-');
if (parseIgnoredOption(optionStart)
|| parseIgnoredOptionAndWarn(optionStart)
+ || parseExperimentalOption(optionStart)
|| parseTestingOption(optionStart)
|| parseUnsupportedOptionAndErr(optionStart)) {
// Intentionally left empty.
@@ -454,10 +465,10 @@
} else if (acceptString("maximumremovedandroidloglevel")) {
skipWhitespace();
Integer maxRemovedAndroidLogLevel = acceptInteger();
- if (maxRemovedAndroidLogLevel != null) {
- configurationBuilder.setMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
+ if (maxRemovedAndroidLogLevel != null && maxRemovedAndroidLogLevel >= 1) {
+ configurationBuilder.joinMaxRemovedAndroidLogLevel(maxRemovedAndroidLogLevel);
} else {
- throw parseError("Expected integer", getPosition());
+ throw parseError("Expected integer greater than or equal to 1", getPosition());
}
} else {
String unknownOption = acceptString();
@@ -472,9 +483,34 @@
return true;
}
+ private boolean parseExperimentalOption(TextPosition optionStart)
+ throws ProguardRuleParserException {
+ if (acceptString(CheckEnumUnboxedRule.RULE_NAME)) {
+ CheckEnumUnboxedRule checkEnumUnboxedRule = parseCheckEnumUnboxedRule(optionStart);
+ if (options.isExperimentalCheckEnumUnboxedEnabled()) {
+ configurationBuilder.addRule(checkEnumUnboxedRule);
+ }
+ return true;
+ }
+ if (acceptString(ConvertCheckNotNullRule.RULE_NAME)) {
+ ConvertCheckNotNullRule convertCheckNotNullRule = parseConvertCheckNotNullRule(optionStart);
+ if (options.isExperimentalConvertCheckNotNullEnabled()) {
+ configurationBuilder.addRule(convertCheckNotNullRule);
+ }
+ return true;
+ }
+ if (options.isExperimentalWhyAreYouNotInliningEnabled()) {
+ if (acceptString(WhyAreYouNotInliningRule.RULE_NAME)) {
+ configurationBuilder.addRule(parseWhyAreYouNotInliningRule(optionStart));
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean parseTestingOption(TextPosition optionStart)
throws ProguardRuleParserException {
- if (allowTestOptions) {
+ if (options.isTestingOptionsEnabled()) {
if (acceptString("assumemayhavesideeffects")) {
ProguardAssumeMayHaveSideEffectsRule rule =
parseAssumeMayHaveSideEffectsRule(optionStart);
@@ -591,11 +627,6 @@
parseReprocessMethodRule(ReprocessMethodRule.Type.ALWAYS, optionStart));
return true;
}
- if (acceptString("whyareyounotinlining")) {
- WhyAreYouNotInliningRule rule = parseWhyAreYouNotInliningRule(optionStart);
- configurationBuilder.addRule(rule);
- return true;
- }
}
return false;
}
@@ -651,19 +682,6 @@
|| parseOptimizationOption(optionStart);
}
- private boolean parseIgnoredReturnValueAttribute() {
- return Iterables.any(IGNORED_RETURN_VALUE_ATTRIBUTES, this::skipReturnValueAttribute);
- }
-
- private boolean parseIgnoredReturnValueAttributes() {
- boolean anySkipped = false;
- while (parseIgnoredReturnValueAttribute()) {
- anySkipped = true;
- skipWhitespace();
- }
- return anySkipped;
- }
-
private void parseInclude() throws ProguardRuleParserException {
TextPosition start = getPosition();
Path included = parseFileInputDependency(inputDependencyConsumer::acceptProguardInclude);
@@ -795,7 +813,7 @@
.setOrigin(origin)
.setStart(start);
parseRuleTypeAndModifiers(keepRuleBuilder);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
if (keepRuleBuilder.getMemberRules().isEmpty()) {
// If there are no member rules, a default rule for the parameterless constructor
// applies. So we add that here.
@@ -817,7 +835,7 @@
ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder()
.setOrigin(origin)
.setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -829,7 +847,7 @@
ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder()
.setOrigin(origin)
.setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -840,7 +858,7 @@
throws ProguardRuleParserException {
ClassInlineRule.Builder keepRuleBuilder =
ClassInlineRule.builder().setOrigin(origin).setStart(start).setType(type);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -851,7 +869,7 @@
throws ProguardRuleParserException {
NoFieldTypeStrengtheningRule.Builder keepRuleBuilder =
NoFieldTypeStrengtheningRule.builder().setOrigin(origin).setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -862,7 +880,7 @@
throws ProguardRuleParserException {
NoUnusedInterfaceRemovalRule.Builder keepRuleBuilder =
NoUnusedInterfaceRemovalRule.builder().setOrigin(origin).setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -873,7 +891,7 @@
throws ProguardRuleParserException {
NoVerticalClassMergingRule.Builder keepRuleBuilder =
NoVerticalClassMergingRule.builder().setOrigin(origin).setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -884,7 +902,7 @@
throws ProguardRuleParserException {
NoHorizontalClassMergingRule.Builder keepRuleBuilder =
NoHorizontalClassMergingRule.builder().setOrigin(origin).setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -894,7 +912,7 @@
private <R extends NoOptimizationBaseRule<R>, B extends NoOptimizationBaseRule.Builder<R, B>>
R parseNoOptimizationRule(Position start, B builder) throws ProguardRuleParserException {
builder.setOrigin(origin).setStart(start);
- parseClassSpec(builder, false);
+ parseClassSpec(builder);
Position end = getPosition();
builder.setSource(getSourceSnippet(contents, start, end));
builder.setEnd(end);
@@ -906,7 +924,7 @@
throws ProguardRuleParserException {
MemberValuePropagationRule .Builder keepRuleBuilder =
MemberValuePropagationRule.builder().setOrigin(origin).setStart(start).setType(type);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -919,7 +937,7 @@
.setOrigin(origin)
.setStart(start)
.setType(type);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -932,7 +950,7 @@
ProguardIdentifierNameStringRule.builder()
.setOrigin(origin)
.setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -944,7 +962,7 @@
ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder()
.setOrigin(origin)
.setStart(optionStart);
- parseClassSpec(ifRuleBuilder, false);
+ parseClassSpec(ifRuleBuilder);
// Required a subsequent keep rule.
skipWhitespace();
@@ -967,7 +985,7 @@
throws ProguardRuleParserException {
KeepConstantArgumentRule.Builder keepRuleBuilder =
KeepConstantArgumentRule.builder().setOrigin(origin).setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -978,7 +996,7 @@
throws ProguardRuleParserException {
KeepUnusedArgumentRule.Builder keepRuleBuilder =
KeepUnusedArgumentRule.builder().setOrigin(origin).setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -990,7 +1008,7 @@
throws ProguardRuleParserException {
ReprocessClassInitializerRule.Builder builder =
ReprocessClassInitializerRule.builder().setOrigin(origin).setStart(start).setType(type);
- parseClassSpec(builder, false);
+ parseClassSpec(builder);
Position end = getPosition();
builder.setSource(getSourceSnippet(contents, start, end));
builder.setEnd(end);
@@ -1001,7 +1019,7 @@
ReprocessMethodRule.Type type, Position start) throws ProguardRuleParserException {
ReprocessMethodRule.Builder builder =
ReprocessMethodRule.builder().setOrigin(origin).setStart(start).setType(type);
- parseClassSpec(builder, false);
+ parseClassSpec(builder);
Position end = getPosition();
builder.setSource(getSourceSnippet(contents, start, end));
builder.setEnd(end);
@@ -1012,7 +1030,7 @@
throws ProguardRuleParserException {
WhyAreYouNotInliningRule.Builder keepRuleBuilder =
WhyAreYouNotInliningRule.builder().setOrigin(origin).setStart(start);
- parseClassSpec(keepRuleBuilder, false);
+ parseClassSpec(keepRuleBuilder);
Position end = getPosition();
keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
keepRuleBuilder.setEnd(end);
@@ -1042,6 +1060,14 @@
}
}
+ private <
+ C extends ProguardClassSpecification,
+ B extends ProguardClassSpecification.Builder<C, B>>
+ void parseClassSpec(ProguardClassSpecification.Builder<C, B> builder)
+ throws ProguardRuleParserException {
+ parseClassSpec(builder, false);
+ }
+
private
<C extends ProguardClassSpecification, B extends ProguardClassSpecification.Builder<C, B>>
void parseClassSpec(
@@ -1106,7 +1132,7 @@
builder.getModifiersBuilder().setAllowsObfuscation(true);
} else if (acceptString("accessmodification")) {
builder.getModifiersBuilder().setAllowsAccessModification(true);
- } else if (allowTestOptions) {
+ } else if (options.isTestingOptionsEnabled()) {
if (acceptString("annotationremoval")) {
builder.getModifiersBuilder().setAllowsAnnotationRemoval(true);
}
@@ -1275,6 +1301,11 @@
flags.setBridge();
}
break;
+ case 'c':
+ if ((found = acceptString("constructor"))) {
+ flags.setConstructor();
+ }
+ break;
case 'f':
if ((found = acceptString("final"))) {
flags.setFinal();
@@ -1420,56 +1451,61 @@
}
skipWhitespace();
// Parse "return ..." if present.
+ TextPosition returnStart = getPosition();
if (acceptString("return")) {
+ if (!allowValueSpecification) {
+ throw parseError("Unexpected value specification", returnStart);
+ }
skipWhitespace();
- boolean anyReturnValueAttributesSkipped = parseIgnoredReturnValueAttributes();
- if (!anyReturnValueAttributesSkipped || !hasNextChar(';')) {
- if (acceptString("true")) {
- ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(true));
- } else if (acceptString("false")) {
- ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(false));
- } else if (acceptString("null")) {
- ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue());
- } else {
- TextPosition fieldOrValueStart = getPosition();
- String qualifiedFieldNameOrInteger = acceptFieldNameOrIntegerForReturn();
- if (qualifiedFieldNameOrInteger != null) {
- if (isInteger(qualifiedFieldNameOrInteger)) {
- Integer min = Integer.parseInt(qualifiedFieldNameOrInteger);
- Integer max = min;
- skipWhitespace();
- if (acceptString("..")) {
- skipWhitespace();
- max = acceptInteger();
- if (max == null) {
- throw parseError("Expected integer value");
- }
- }
- if (!allowValueSpecification) {
- throw parseError("Unexpected value specification", fieldOrValueStart);
- }
- ruleBuilder.setReturnValue(
- new ProguardMemberRuleReturnValue(new LongInterval(min, max)));
- } else {
- if (ruleBuilder.getTypeMatcher() instanceof MatchSpecificType) {
- int lastDotIndex = qualifiedFieldNameOrInteger.lastIndexOf(".");
- DexType fieldType =
- ((MatchSpecificType) ruleBuilder.getTypeMatcher()).type;
- DexType fieldClass =
- dexItemFactory.createType(
- javaTypeToDescriptor(
- qualifiedFieldNameOrInteger.substring(0, lastDotIndex)));
- DexString fieldName =
- dexItemFactory.createString(
- qualifiedFieldNameOrInteger.substring(lastDotIndex + 1));
- DexField field =
- dexItemFactory.createField(fieldClass, fieldType, fieldName);
- ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(field));
- } else {
- throw parseError("Expected specific type", fieldOrValueStart);
- }
+ if (acceptString("true")) {
+ ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(true));
+ } else if (acceptString("false")) {
+ ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(false));
+ } else if (acceptString("null")) {
+ ruleBuilder.setReturnValue(
+ new ProguardMemberRuleReturnValue(Nullability.definitelyNull()));
+ } else {
+ Integer integer = acceptInteger();
+ if (integer != null) {
+ Integer min = integer;
+ Integer max = min;
+ skipWhitespace();
+ if (acceptString("..")) {
+ skipWhitespace();
+ max = acceptInteger();
+ if (max == null) {
+ throw parseError("Expected integer value");
}
}
+ ruleBuilder.setReturnValue(
+ new ProguardMemberRuleReturnValue(new LongInterval(min, max)));
+ } else {
+ Nullability nullability = Nullability.maybeNull();
+ if (acceptString("_NONNULL_")) {
+ nullability = Nullability.definitelyNotNull();
+ skipWhitespace();
+ if (acceptChar(';')) {
+ ruleBuilder.setReturnValue(
+ new ProguardMemberRuleReturnValue(nullability));
+ return;
+ }
+ }
+ String qualifiedFieldName = acceptQualifiedFieldName();
+ if (qualifiedFieldName != null) {
+ int lastDotIndex = qualifiedFieldName.lastIndexOf(".");
+ DexType fieldHolder =
+ dexItemFactory.createType(
+ javaTypeToDescriptor(
+ qualifiedFieldName.substring(0, lastDotIndex)));
+ DexString fieldName =
+ dexItemFactory.createString(
+ qualifiedFieldName.substring(lastDotIndex + 1));
+ ruleBuilder.setReturnValue(
+ new ProguardMemberRuleReturnValue(
+ fieldHolder, fieldName, nullability));
+ } else {
+ throw parseError("Expected qualified field");
+ }
}
}
}
@@ -1670,7 +1706,7 @@
throws ProguardRuleParserException {
ProguardAssumeMayHaveSideEffectsRule.Builder builder =
ProguardAssumeMayHaveSideEffectsRule.builder().setOrigin(origin).setStart(start);
- parseClassSpec(builder, true);
+ parseClassSpec(builder);
Position end = getPosition();
builder.setSource(getSourceSnippet(contents, start, end));
builder.setEnd(end);
@@ -1689,6 +1725,28 @@
return builder.build();
}
+ private CheckEnumUnboxedRule parseCheckEnumUnboxedRule(Position start)
+ throws ProguardRuleParserException {
+ CheckEnumUnboxedRule.Builder builder =
+ CheckEnumUnboxedRule.builder().setOrigin(origin).setStart(start);
+ parseClassSpec(builder);
+ Position end = getPosition();
+ builder.setSource(getSourceSnippet(contents, start, end));
+ builder.setEnd(end);
+ return builder.build();
+ }
+
+ private ConvertCheckNotNullRule parseConvertCheckNotNullRule(Position start)
+ throws ProguardRuleParserException {
+ ConvertCheckNotNullRule.Builder builder =
+ ConvertCheckNotNullRule.builder().setOrigin(origin).setStart(start);
+ parseClassSpec(builder);
+ Position end = getPosition();
+ builder.setSource(getSourceSnippet(contents, start, end));
+ builder.setEnd(end);
+ return builder.build();
+ }
+
private void skipWhitespace() {
while (!eof() && StringUtils.isWhitespace(peekChar())) {
if (peekChar() == '\n') {
@@ -1978,27 +2036,35 @@
contents.substring(start, end), wildcardsCollector.build(), negated);
}
- private String acceptFieldNameOrIntegerForReturn() {
+ private String acceptQualifiedFieldName() {
skipWhitespace();
int start = position;
- int end = position;
+ // A qualified field name must be non empty.
+ if (eof(start)) {
+ return null;
+ }
+ // The first character of a qualified field name is an identifier part.
+ int firstCodePoint = contents.codePointAt(start);
+ if (!IdentifierUtils.isDexIdentifierStart(contents.codePointAt(start))) {
+ return null;
+ }
+ int end = start + Character.charCount(firstCodePoint);
while (!eof(end)) {
- int current = contents.codePointAt(end);
- if (current == '.' && !eof(end + 1) && peekCharAt(end + 1) == '.') {
- // The grammar is ambiguous. End accepting before .. token used in return ranges.
- break;
- }
- if ((start == end && IdentifierUtils.isDexIdentifierStart(current))
- || ((start < end)
- && (IdentifierUtils.isDexIdentifierPart(current) || current == '.'))) {
- end += Character.charCount(current);
+ int currentCodePoint = contents.codePointAt(end);
+ if (currentCodePoint == '.') {
+ end += 1;
+ // Each dot in a qualified field name must be followed by an identifier part.
+ if (!eof(end) && IdentifierUtils.isDexIdentifierPart(peekCharAt(end))) {
+ end += Character.charCount(contents.codePointAt(end));
+ } else {
+ break;
+ }
+ } else if (IdentifierUtils.isDexIdentifierPart(currentCodePoint)) {
+ end += Character.charCount(currentCodePoint);
} else {
break;
}
}
- if (start == end) {
- return null;
- }
position = end;
return contents.substring(start, end);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
new file mode 100644
index 0000000..8811df7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault;
+
+public class ProguardConfigurationParserOptions {
+
+ private final boolean enableExperimentalCheckEnumUnboxed;
+ private final boolean enableExperimentalConvertCheckNotNull;
+ private final boolean enableExperimentalWhyAreYouNotInlining;
+ private final boolean enableTestingOptions;
+
+ ProguardConfigurationParserOptions(
+ boolean enableExperimentalCheckEnumUnboxed,
+ boolean enableExperimentalConvertCheckNotNull,
+ boolean enableExperimentalWhyAreYouNotInlining,
+ boolean enableTestingOptions) {
+ this.enableExperimentalCheckEnumUnboxed = enableExperimentalCheckEnumUnboxed;
+ this.enableExperimentalConvertCheckNotNull = enableExperimentalConvertCheckNotNull;
+ this.enableExperimentalWhyAreYouNotInlining = enableExperimentalWhyAreYouNotInlining;
+ this.enableTestingOptions = enableTestingOptions;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public boolean isExperimentalCheckEnumUnboxedEnabled() {
+ return enableExperimentalCheckEnumUnboxed;
+ }
+
+ public boolean isExperimentalConvertCheckNotNullEnabled() {
+ return enableExperimentalConvertCheckNotNull;
+ }
+
+ public boolean isExperimentalWhyAreYouNotInliningEnabled() {
+ return enableExperimentalWhyAreYouNotInlining;
+ }
+
+ public boolean isTestingOptionsEnabled() {
+ return enableTestingOptions;
+ }
+
+ public static class Builder {
+
+ private boolean enableExperimentalCheckEnumUnboxed;
+ private boolean enableExperimentalConvertCheckNotNull;
+ private boolean enableExperimentalWhyAreYouNotInlining;
+ private boolean enableTestingOptions;
+
+ public Builder readEnvironment() {
+ enableExperimentalCheckEnumUnboxed =
+ parseSystemPropertyOrDefault(
+ "com.android.tools.r8.experimental.enablecheckenumunboxed", false);
+ enableExperimentalConvertCheckNotNull =
+ parseSystemPropertyOrDefault(
+ "com.android.tools.r8.experimental.enableconvertchecknotnull", false);
+ enableExperimentalWhyAreYouNotInlining =
+ parseSystemPropertyOrDefault(
+ "com.android.tools.r8.experimental.enablewhyareyounotinlining", false);
+ enableTestingOptions =
+ parseSystemPropertyOrDefault("com.android.tools.r8.allowTestProguardOptions", false);
+ return this;
+ }
+
+ public Builder setEnableExperimentalCheckEnumUnboxed(
+ boolean enableExperimentalCheckEnumUnboxed) {
+ this.enableExperimentalCheckEnumUnboxed = enableExperimentalCheckEnumUnboxed;
+ return this;
+ }
+
+ public Builder setEnableExperimentalConvertCheckNotNull(
+ boolean enableExperimentalConvertCheckNotNull) {
+ this.enableExperimentalConvertCheckNotNull = enableExperimentalConvertCheckNotNull;
+ return this;
+ }
+
+ public Builder setEnableExperimentalWhyAreYouNotInlining(
+ boolean enableExperimentalWhyAreYouNotInlining) {
+ this.enableExperimentalWhyAreYouNotInlining = enableExperimentalWhyAreYouNotInlining;
+ return this;
+ }
+
+ public Builder setEnableTestingOptions(boolean enableTestingOptions) {
+ this.enableTestingOptions = enableTestingOptions;
+ return this;
+ }
+
+ public ProguardConfigurationParserOptions build() {
+ return new ProguardConfigurationParserOptions(
+ enableExperimentalCheckEnumUnboxed,
+ enableExperimentalConvertCheckNotNull,
+ enableExperimentalWhyAreYouNotInlining,
+ enableTestingOptions);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index bf21d1f..94ab239 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -4,89 +4,16 @@
package com.android.tools.r8.shaking;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.LongInterval;
import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
import java.util.List;
-import java.util.stream.Collectors;
public class ProguardConfigurationUtils {
- private static Origin proguardCompatOrigin =
- new Origin(Origin.root()) {
- @Override
- public String part() {
- return "<PROGUARD_COMPATIBILITY_RULE>";
- }
- };
-
- private static Origin synthesizedRecompilationOrigin =
- new Origin(Origin.root()) {
- @Override
- public String part() {
- return "<SYNTHESIZED_RECOMPILATION_RULE>";
- }
- };
-
- public static ProguardKeepRule buildDefaultInitializerKeepRule(DexClass clazz) {
- ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
- builder.setOrigin(proguardCompatOrigin);
- builder.setType(ProguardKeepRuleType.KEEP);
- builder.getModifiersBuilder().setAllowsObfuscation(true);
- builder.getModifiersBuilder().setAllowsOptimization(true);
- builder.getClassAccessFlags().setVisibility(clazz.accessFlags);
- builder.setClassType(ProguardClassType.CLASS);
- builder.setClassNames(
- ProguardClassNameList.singletonList(ProguardTypeMatcher.create(clazz.type)));
- if (clazz.hasDefaultInitializer()) {
- ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
- memberRuleBuilder.setRuleType(ProguardMemberType.INIT);
- memberRuleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
- memberRuleBuilder.setArguments(ImmutableList.of());
- builder.getMemberRules().add(memberRuleBuilder.build());
- }
- return builder.build();
- }
-
- public static ProguardKeepRule buildMethodKeepRule(DexClass clazz, DexEncodedMethod method) {
- // TODO(b/122295241): These generated rules should be linked into the graph, eg, the method
- // using identified reflection should be the source keeping the target alive.
- assert clazz.type == method.getHolderType();
- ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
- builder.setOrigin(proguardCompatOrigin);
- builder.setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
- builder.getModifiersBuilder().setAllowsObfuscation(true);
- builder.getModifiersBuilder().setAllowsOptimization(true);
- builder.getClassAccessFlags().setVisibility(clazz.accessFlags);
- if (clazz.isInterface()) {
- builder.setClassType(ProguardClassType.INTERFACE);
- } else {
- builder.setClassType(ProguardClassType.CLASS);
- }
- builder.setClassNames(
- ProguardClassNameList.singletonList(ProguardTypeMatcher.create(clazz.type)));
- ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
- memberRuleBuilder.setRuleType(ProguardMemberType.METHOD);
- memberRuleBuilder.getAccessFlags().setFlags(method.accessFlags);
- memberRuleBuilder.setName(
- IdentifierPatternWithWildcards.withoutWildcards(method.getReference().name.toString()));
- memberRuleBuilder.setTypeMatcher(
- ProguardTypeMatcher.create(method.getReference().proto.returnType));
- List<ProguardTypeMatcher> arguments =
- Arrays.stream(method.getReference().proto.parameters.values)
- .map(ProguardTypeMatcher::create)
- .collect(Collectors.toList());
- memberRuleBuilder.setArguments(arguments);
- builder.getMemberRules().add(memberRuleBuilder.build());
- return builder.build();
- }
-
public static ProguardAssumeNoSideEffectRule buildAssumeNoSideEffectsRuleForApiLevel(
DexItemFactory factory, AndroidApiLevel apiLevel) {
Origin synthesizedFromApiLevel =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
index 11b8868..9f7cbc7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRuleReturnValue.java
@@ -4,68 +4,99 @@
package com.android.tools.r8.shaking;
-import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
+import com.android.tools.r8.ir.analysis.value.objectstate.ObjectState;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.LongInterval;
public class ProguardMemberRuleReturnValue {
- public enum Type {
+
+ private enum Type {
BOOLEAN,
- VALUE_RANGE,
FIELD,
- NULL
+ NULLABILITY,
+ VALUE_RANGE
}
private final Type type;
private final boolean booleanValue;
private final LongInterval longInterval;
- private final DexField field;
+ private final DexType fieldHolder;
+ private final DexString fieldName;
+ private final Nullability nullability;
ProguardMemberRuleReturnValue(boolean value) {
this.type = Type.BOOLEAN;
this.booleanValue = value;
this.longInterval = null;
- this.field = null;
+ this.fieldHolder = null;
+ this.fieldName = null;
+ this.nullability = null;
+ }
+
+ @SuppressWarnings("InconsistentOverloads")
+ ProguardMemberRuleReturnValue(DexType fieldHolder, DexString fieldName, Nullability nullability) {
+ assert !nullability.isDefinitelyNull();
+ this.type = Type.FIELD;
+ this.booleanValue = false;
+ this.longInterval = null;
+ this.fieldHolder = fieldHolder;
+ this.fieldName = fieldName;
+ this.nullability = nullability;
+ }
+
+ ProguardMemberRuleReturnValue(Nullability nullability) {
+ assert nullability.isDefinitelyNull() || nullability.isDefinitelyNotNull();
+ this.type = Type.NULLABILITY;
+ this.booleanValue = false;
+ this.longInterval = null;
+ this.fieldHolder = null;
+ this.fieldName = null;
+ this.nullability = nullability;
}
ProguardMemberRuleReturnValue(LongInterval value) {
this.type = Type.VALUE_RANGE;
this.booleanValue = false;
this.longInterval = value;
- this.field = null;
+ this.fieldHolder = null;
+ this.fieldName = null;
+ this.nullability = getNullabilityForValueRange(value);
}
- ProguardMemberRuleReturnValue(DexField field) {
- this.type = Type.FIELD;
- this.booleanValue = false;
- this.longInterval = null;
- this.field = field;
- }
-
- ProguardMemberRuleReturnValue() {
- this.type = Type.NULL;
- this.booleanValue = false;
- this.longInterval = null;
- this.field = null;
+ private static Nullability getNullabilityForValueRange(LongInterval value) {
+ if (value.isSingleValue(0)) {
+ return Nullability.definitelyNull();
+ } else if (!value.containsValue(0)) {
+ return Nullability.definitelyNotNull();
+ } else {
+ return Nullability.maybeNull();
+ }
}
public boolean isBoolean() {
return type == Type.BOOLEAN;
}
- public boolean isValueRange() {
- return type == Type.VALUE_RANGE;
- }
-
public boolean isField() {
return type == Type.FIELD;
}
- public boolean isNonNull() {
- return isValueRange() && getValueRange().getMin() > 0;
+ public boolean isNullability() {
+ return type == Type.NULLABILITY;
}
- public boolean isNull() {
- return type == Type.NULL;
+ public boolean isValueRange() {
+ return type == Type.VALUE_RANGE;
}
public boolean getBoolean() {
@@ -73,31 +104,23 @@
return booleanValue;
}
- /**
- * Returns if this return value is a single value.
- *
- * Boolean values and null are considered a single value.
- */
- public boolean isSingleValue() {
- return isBoolean() || isNull() || (isValueRange() && longInterval.isSingleValue());
+ public DexType getFieldHolder() {
+ assert isField();
+ return fieldHolder;
}
- /**
- * Returns the return value.
- *
- * Boolean values are returned as 0 for <code>false</code> and 1 for <code>true</code>.
- *
- * Reference value <code>null</code> is returned as 0.
- */
- public long getSingleValue() {
- assert isSingleValue();
- if (isBoolean()) {
- return booleanValue ? 1 : 0;
- }
- if (isNull()) {
- return 0;
- }
- return longInterval.getSingleValue();
+ public DexString getFieldName() {
+ assert isField();
+ return fieldName;
+ }
+
+ private boolean hasNullability() {
+ return isField() || isNullability() || isValueRange();
+ }
+
+ public Nullability getNullability() {
+ assert hasNullability();
+ return nullability;
}
public LongInterval getValueRange() {
@@ -105,9 +128,48 @@
return longInterval;
}
- public DexField getField() {
- assert isField();
- return field;
+ public AbstractValue toAbstractValue(AppView<?> appView, DexType valueType) {
+ AbstractValueFactory abstractValueFactory = appView.abstractValueFactory();
+ switch (type) {
+ case BOOLEAN:
+ return abstractValueFactory.createSingleNumberValue(BooleanUtils.intValue(booleanValue));
+
+ case FIELD:
+ DexClass holder = appView.definitionFor(fieldHolder);
+ if (holder != null) {
+ DexEncodedField field = holder.lookupUniqueStaticFieldWithName(fieldName);
+ if (field != null) {
+ return abstractValueFactory.createSingleFieldValue(
+ field.getReference(), ObjectState.empty());
+ }
+ }
+ return AbstractValue.unknown();
+
+ case NULLABILITY:
+ return nullability.isDefinitelyNull()
+ ? abstractValueFactory.createNullValue()
+ : AbstractValue.unknown();
+
+ case VALUE_RANGE:
+ if (valueType.isReferenceType()) {
+ return nullability.isDefinitelyNull()
+ ? abstractValueFactory.createNullValue()
+ : AbstractValue.unknown();
+ }
+ return longInterval.isSingleValue()
+ ? abstractValueFactory.createSingleNumberValue(longInterval.getSingleValue())
+ : abstractValueFactory.createNumberFromIntervalValue(
+ longInterval.getMin(), longInterval.getMax());
+
+ default:
+ throw new Unreachable("Unexpected type: " + type);
+ }
+ }
+
+ public DynamicType toDynamicType(AppView<?> appView, DexType valueType) {
+ return valueType.isReferenceType() && hasNullability() && getNullability().isDefinitelyNotNull()
+ ? DynamicType.definitelyNotNull()
+ : DynamicType.unknown();
}
@Override
@@ -115,18 +177,21 @@
StringBuilder result = new StringBuilder();
result.append(" return ");
if (isBoolean()) {
- result.append(booleanValue ? "true" : "false");
- } else if (isNull()) {
- result.append("null");
- } else if (isValueRange()) {
+ result.append(booleanValue);
+ } else if (isField()) {
+ if (nullability.isDefinitelyNotNull()) {
+ result.append("_NONNULL_ ");
+ }
+ result.append(fieldHolder.getTypeName()).append('.').append(fieldName);
+ } else if (isNullability()) {
+ result.append(nullability.isDefinitelyNull() ? "null" : "_NONNULL_");
+ } else {
+ assert isValueRange();
result.append(longInterval.getMin());
- if (!isSingleValue()) {
+ if (!longInterval.isSingleValue()) {
result.append("..");
result.append(longInterval.getMax());
}
- } else {
- assert isField();
- result.append(field.holder.toSourceString() + '.' + field.name);
}
return result.toString();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 471f865..d804fa6 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic;
+import com.android.tools.r8.errors.AssumeValuesMissingStaticFieldDiagnostic;
import com.android.tools.r8.errors.InlinableStaticFinalFieldPreconditionDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -45,9 +46,12 @@
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
+import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper;
import com.android.tools.r8.ir.desugar.itf.InterfaceMethodDesugaringBaseEventConsumer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.ir.optimize.membervaluepropagation.assume.AssumeInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult;
import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult;
@@ -105,6 +109,7 @@
public static class RootSetBuilder {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
+ private AssumeInfoCollection.Builder assumeInfoCollectionBuilder;
private final SubtypingInfo subtypingInfo;
private final DirectMappedDexApplication application;
private final Iterable<? extends ProguardConfigurationRule> rules;
@@ -127,8 +132,6 @@
new IdentityHashMap<>();
private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects =
new IdentityHashMap<>();
- private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
- private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
private final Set<DexMember<?, ?>> identifierNameStrings = Sets.newIdentityHashSet();
private final Map<DexMethod, ProgramMethod> keptMethodBridges = new ConcurrentHashMap<>();
private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems =
@@ -176,6 +179,12 @@
// Intentionally empty.
}
+ public RootSetBuilder setAssumeInfoCollectionBuilder(
+ AssumeInfoCollection.Builder assumeInfoCollectionBuilder) {
+ this.assumeInfoCollectionBuilder = assumeInfoCollectionBuilder;
+ return this;
+ }
+
// Process a class with the keep rule.
private void process(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) {
if (!satisfyClassType(rule, clazz)) {
@@ -252,17 +261,23 @@
throw new Unreachable("-if rule will be evaluated separately, not here.");
} else if (rule.isProguardCheckDiscardRule()) {
evaluateCheckDiscardRule(clazz, rule.asProguardCheckDiscardRule());
+ } else if (rule instanceof CheckEnumUnboxedRule) {
+ evaluateCheckEnumUnboxedRule(clazz, (CheckEnumUnboxedRule) rule);
} else if (rule instanceof ProguardWhyAreYouKeepingRule) {
markClass(clazz, rule, ifRule);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
- } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule
- || rule instanceof ProguardAssumeNoSideEffectRule
- || rule instanceof ProguardAssumeValuesRule) {
+ } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
- markMatchingOverriddenMethods(
- appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingOverriddenMethods(clazz, memberKeepRules, rule, null, true, ifRule);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+ } else if (rule instanceof ProguardAssumeNoSideEffectRule
+ || rule instanceof ProguardAssumeValuesRule) {
+ if (assumeInfoCollectionBuilder != null) {
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingOverriddenMethods(clazz, memberKeepRules, rule, null, true, ifRule);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
+ }
} else if (rule instanceof NoFieldTypeStrengtheningRule) {
markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
} else if (rule instanceof InlineRule
@@ -287,10 +302,12 @@
} else if (rule instanceof MemberValuePropagationRule) {
markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule);
- } else {
- assert rule instanceof ProguardIdentifierNameStringRule;
+ } else if (rule instanceof ProguardIdentifierNameStringRule) {
markMatchingFields(clazz, memberKeepRules, rule, null, ifRule);
markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
+ } else {
+ assert rule instanceof ConvertCheckNotNullRule;
+ markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule);
}
}
@@ -349,7 +366,7 @@
}
finalizeCheckDiscardedInformation();
generateAssumeNoSideEffectsWarnings();
- if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) {
+ if (assumeInfoCollectionBuilder != null && !assumeInfoCollectionBuilder.isEmpty()) {
BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo)
.visit(appView.appInfo().classes(), this::propagateAssumeRules);
}
@@ -381,8 +398,6 @@
noHorizontalClassMerging,
neverPropagateValue,
mayHaveSideEffects,
- noSideEffects,
- assumedValues,
dependentKeepClassCompatRule,
identifierNameStrings,
ifRules,
@@ -401,17 +416,12 @@
assert !encodedMethod.shouldNotHaveCode();
continue;
}
- propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, noSideEffects);
- propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, assumedValues);
+ propagateAssumeRules(clazz, encodedMethod.getReference(), subTypes);
}
}
- private void propagateAssumeRules(
- DexType type,
- DexMethod reference,
- Set<DexType> subTypes,
- Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) {
- ProguardMemberRule ruleToBePropagated = null;
+ private void propagateAssumeRules(DexClass clazz, DexMethod reference, Set<DexType> subTypes) {
+ AssumeInfo infoToBePropagated = null;
for (DexType subType : subTypes) {
DexMethod referenceInSubType =
appView.dexItemFactory().createMethod(subType, reference.proto, reference.name);
@@ -419,34 +429,34 @@
// override the method, and when the retrieval of bound rule fails, it is unclear whether it
// is due to the lack of the definition or it indeed means no matching rules. Similar to how
// we apply those assume rules, here we use a resolved target.
- DexEncodedMethod target =
+ DexClassAndMethod target =
appView
.appInfo()
.unsafeResolveMethodDueToDexFormatLegacy(referenceInSubType)
- .getSingleTarget();
+ .getResolutionPair();
// But, the resolution should not be landed on the current type we are visiting.
- if (target == null || target.getHolderType() == type) {
+ if (target == null || target.getHolder() == clazz) {
continue;
}
- ProguardMemberRule ruleInSubType = assumeRulePool.get(target.getReference());
+ AssumeInfo ruleInSubType = assumeInfoCollectionBuilder.buildInfo(target);
// We are looking for the greatest lower bound of assume rules from all sub types.
// If any subtype doesn't have a matching assume rule, the lower bound is literally nothing.
if (ruleInSubType == null) {
- ruleToBePropagated = null;
+ infoToBePropagated = null;
break;
}
- if (ruleToBePropagated == null) {
- ruleToBePropagated = ruleInSubType;
+ if (infoToBePropagated == null) {
+ infoToBePropagated = ruleInSubType;
} else {
// TODO(b/133208961): Introduce comparison/meet of assume rules.
- if (!ruleToBePropagated.equals(ruleInSubType)) {
- ruleToBePropagated = null;
+ if (!infoToBePropagated.equals(ruleInSubType)) {
+ infoToBePropagated = null;
break;
}
}
}
- if (ruleToBePropagated != null) {
- assumeRulePool.put(reference, ruleToBePropagated);
+ if (infoToBePropagated != null) {
+ assumeInfoCollectionBuilder.meet(reference, infoToBePropagated);
}
}
@@ -663,7 +673,6 @@
}
private void markMatchingOverriddenMethods(
- AppInfoWithClassHierarchy appInfoWithSubtyping,
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
@@ -1156,36 +1165,17 @@
return;
}
evaluateKeepRule(
- item.asProgramDefinition(), context.asProguardKeepRule(), rule, precondition, ifRule);
+ item.asProgramDefinition(), context.asProguardKeepRule(), precondition, ifRule);
} else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) {
mayHaveSideEffects.put(item.getReference(), rule);
context.markAsUsed();
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
- if (item.isMember()) {
- DexClassAndMember<?, ?> member = item.asMember();
- if (member.getHolderType() == appView.dexItemFactory().objectType) {
- assert member.isMethod();
- reportAssumeNoSideEffectsWarningForJavaLangClassMethod(
- member.asMethod(), (ProguardAssumeNoSideEffectRule) context);
- } else {
- noSideEffects.put(member.getReference(), rule);
- if (member.isMethod()) {
- DexClassAndMethod method = member.asMethod();
- if (method.getDefinition().isClassInitializer()) {
- feedback.classInitializerMayBePostponed(method.getDefinition());
- }
- }
- }
- context.markAsUsed();
- }
+ evaluateAssumeNoSideEffectsRule(item, (ProguardAssumeNoSideEffectRule) context, rule);
+ } else if (context instanceof ProguardAssumeValuesRule) {
+ evaluateAssumeValuesRule(item, (ProguardAssumeValuesRule) context, rule);
} else if (context instanceof ProguardWhyAreYouKeepingRule) {
reasonAsked.computeIfAbsent(item.getReference(), i -> i);
context.markAsUsed();
- } else if (context instanceof ProguardAssumeValuesRule) {
- if (item.isMember()) {
- assumedValues.put(item.asMember().getReference(), rule);
- context.markAsUsed();
- }
} else if (context.isProguardCheckDiscardRule()) {
assert item.isProgramMember();
evaluateCheckDiscardMemberRule(
@@ -1364,6 +1354,17 @@
.asMethodJoiner()
.disallowUnusedReturnValueOptimization();
context.markAsUsed();
+ } else if (context instanceof ConvertCheckNotNullRule) {
+ assert item.isMethod();
+ feedback.setConvertCheckNotNull(item.asMethod());
+ if (item.isProgramMethod()) {
+ // Disallow optimization to prevent inlining.
+ dependentMinimumKeepInfo
+ .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+ .asMethodJoiner()
+ .disallowOptimization();
+ }
+ context.markAsUsed();
} else {
throw new Unreachable();
}
@@ -1427,10 +1428,90 @@
}
}
+ private void evaluateAssumeNoSideEffectsRule(
+ Definition item, ProguardAssumeNoSideEffectRule context, ProguardMemberRule rule) {
+ assert assumeInfoCollectionBuilder != null;
+ if (!item.isMember()) {
+ return;
+ }
+ DexClassAndMember<?, ?> member = item.asMember();
+ if (member.getHolderType() == appView.dexItemFactory().objectType) {
+ assert member.isMethod();
+ reportAssumeNoSideEffectsWarningForJavaLangClassMethod(member.asMethod(), context);
+ } else {
+ DexType valueType =
+ member.getReference().apply(DexField::getType, DexMethod::getReturnType);
+ assumeInfoCollectionBuilder
+ .applyIf(
+ rule.hasReturnValue(),
+ builder -> {
+ DynamicType assumeType = rule.getReturnValue().toDynamicType(appView, valueType);
+ AbstractValue assumeValue =
+ rule.getReturnValue().toAbstractValue(appView, valueType);
+ builder.meetAssumeType(member, assumeType).meetAssumeValue(member, assumeValue);
+ reportAssumeValuesWarningForMissingReturnField(context, rule, assumeValue);
+ })
+ .setIsSideEffectFree(member);
+ if (member.isMethod()) {
+ DexClassAndMethod method = member.asMethod();
+ if (method.getDefinition().isClassInitializer()) {
+ feedback.classInitializerMayBePostponed(method.getDefinition());
+ }
+ }
+ }
+ context.markAsUsed();
+ }
+
+ private void evaluateAssumeValuesRule(
+ Definition item, ProguardAssumeValuesRule context, ProguardMemberRule rule) {
+ assert assumeInfoCollectionBuilder != null;
+ if (!item.isMember() || !rule.hasReturnValue()) {
+ return;
+ }
+ DexClassAndMember<?, ?> member = item.asMember();
+ DexType valueType = member.getReference().apply(DexField::getType, DexMethod::getReturnType);
+ DynamicType assumeType = rule.getReturnValue().toDynamicType(appView, valueType);
+ AbstractValue assumeValue = rule.getReturnValue().toAbstractValue(appView, valueType);
+ assumeInfoCollectionBuilder
+ .meetAssumeType(member, assumeType)
+ .meetAssumeValue(member, assumeValue);
+ reportAssumeValuesWarningForMissingReturnField(context, rule, assumeValue);
+ context.markAsUsed();
+ }
+
+ private void evaluateCheckEnumUnboxedRule(DexClass clazz, CheckEnumUnboxedRule rule) {
+ if (clazz.isProgramClass()) {
+ if (clazz.isEnum()) {
+ dependentMinimumKeepInfo
+ .getOrCreateUnconditionalMinimumKeepInfo()
+ .getOrCreateMinimumKeepInfoFor(clazz.getType())
+ .asClassJoiner()
+ .setCheckEnumUnboxed();
+ } else {
+ StringDiagnostic warning =
+ new StringDiagnostic(
+ "The rule `"
+ + rule
+ + "` matches the non-enum class "
+ + clazz.getTypeName()
+ + ".");
+ appView.reporter().warning(warning);
+ }
+ } else {
+ StringDiagnostic warning =
+ new StringDiagnostic(
+ "The rule `"
+ + rule
+ + "` matches the non-program class "
+ + clazz.getTypeName()
+ + ".");
+ appView.reporter().warning(warning);
+ }
+ }
+
private void evaluateKeepRule(
ProgramDefinition item,
ProguardKeepRule context,
- ProguardMemberRule rule,
DexProgramClass precondition,
ProguardIfRule ifRule) {
if (item.isField()) {
@@ -1663,6 +1744,22 @@
.build());
});
}
+
+ private void reportAssumeValuesWarningForMissingReturnField(
+ ProguardConfigurationRule context, ProguardMemberRule rule, AbstractValue assumeValue) {
+ if (rule.hasReturnValue() && rule.getReturnValue().isField()) {
+ assert assumeValue.isSingleFieldValue() || assumeValue.isUnknown();
+ if (assumeValue.isUnknown()) {
+ ProguardMemberRuleReturnValue returnValue = rule.getReturnValue();
+ options.reporter.warning(
+ new AssumeValuesMissingStaticFieldDiagnostic.Builder()
+ .setField(returnValue.getFieldHolder(), returnValue.getFieldName())
+ .setOrigin(context.getOrigin())
+ .setPosition(context.getPosition())
+ .build());
+ }
+ }
+ }
}
abstract static class RootSetBase {
@@ -1712,8 +1809,6 @@
public final Set<DexType> noHorizontalClassMerging;
public final Set<DexMember<?, ?>> neverPropagateValue;
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
- public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects;
- public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues;
public final Set<DexMember<?, ?>> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
@@ -1733,8 +1828,6 @@
Set<DexType> noHorizontalClassMerging,
Set<DexMember<?, ?>> neverPropagateValue,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects,
- Map<DexMember<?, ?>, ProguardMemberRule> assumedValues,
Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
Set<DexMember<?, ?>> identifierNameStrings,
Set<ProguardIfRule> ifRules,
@@ -1759,8 +1852,6 @@
this.noHorizontalClassMerging = noHorizontalClassMerging;
this.neverPropagateValue = neverPropagateValue;
this.mayHaveSideEffects = mayHaveSideEffects;
- this.noSideEffects = noSideEffects;
- this.assumedValues = assumedValues;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
this.ifRules = Collections.unmodifiableSet(ifRules);
}
@@ -1819,7 +1910,6 @@
pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer);
pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer);
pruneDeadReferences(alwaysInline, definitions, enqueuer);
- pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer);
}
private static void pruneDeadReferences(
@@ -1871,8 +1961,6 @@
noHorizontalClassMerging,
neverPropagateValue,
mayHaveSideEffects,
- noSideEffects,
- assumedValues,
dependentKeepClassCompatRule,
identifierNameStrings,
ifRules,
@@ -2044,8 +2132,6 @@
StringBuilder builder = new StringBuilder();
builder.append("RootSet");
builder.append("\nreasonAsked: " + reasonAsked.size());
- builder.append("\nnoSideEffects: " + noSideEffects.size());
- builder.append("\nassumedValues: " + assumedValues.size());
builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
builder.append("\nifRules: " + ifRules.size());
return builder.toString();
@@ -2164,8 +2250,6 @@
Collections.emptySet(),
emptyMap(),
emptyMap(),
- emptyMap(),
- emptyMap(),
Collections.emptySet(),
ifRules,
delayedRootSetActionItems,
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index f3391c8..7170817 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -124,6 +124,9 @@
Log.debug(getClass(), "Removing class: " + clazz);
}
prunedTypes.add(clazz.type);
+ if (clazz.getSourceFile() != null) {
+ appView.addPrunedClassSourceFile(clazz.type, clazz.getSourceFile().toString());
+ }
unusedItemsPrinter.registerUnusedClass(clazz);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
index 0f2f684..eff85ec 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
@@ -9,6 +9,8 @@
public class WhyAreYouNotInliningRule extends ProguardConfigurationRule {
+ public static final String RULE_NAME = "whyareyounotinlining";
+
public static class Builder
extends ProguardConfigurationRule.Builder<WhyAreYouNotInliningRule, Builder> {
@@ -76,6 +78,6 @@
@Override
String typeString() {
- return "whyareyounotinlining";
+ return RULE_NAME;
}
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index 598dc4f..e9951c3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.NestHostClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import java.util.ArrayList;
@@ -171,6 +172,7 @@
abstractFlag | finalFlag | itfFlag | Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
NestHostClassAttribute nestHost = null;
List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
+ List<PermittedSubclassAttribute> permittedSubclasses = Collections.emptyList();
EnclosingMethodAttribute enclosingMembers = null;
List<InnerClassAttribute> innerClasses = Collections.emptyList();
for (SyntheticMethodBuilder builder : methods) {
@@ -198,6 +200,7 @@
sourceFile,
nestHost,
nestMembers,
+ permittedSubclasses,
enclosingMembers,
innerClasses,
signature,
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
index 8eb6b51..374459a 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevelUtils.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.androidapi.ComputedApiLevel;
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.LibraryMethod;
import com.android.tools.r8.graph.ProgramMethod;
@@ -78,6 +79,7 @@
public static boolean isApiSafeForMemberRebinding(
LibraryMethod method,
+ DexMethod original,
AndroidApiLevelCompute androidApiLevelCompute,
InternalOptions options) {
ComputedApiLevel apiLevel =
@@ -87,6 +89,17 @@
return false;
}
assert options.apiModelingOptions().enableApiCallerIdentification;
- return apiLevel.asKnownApiLevel().getApiLevel().isLessThanOrEqualTo(options.getMinApiLevel());
+ ComputedApiLevel apiLevelOfOriginal =
+ androidApiLevelCompute.computeApiLevelForLibraryReference(
+ original, ComputedApiLevel.unknown());
+ if (apiLevelOfOriginal.isUnknownApiLevel()) {
+ return false;
+ }
+ return apiLevelOfOriginal
+ .asKnownApiLevel()
+ .max(apiLevel)
+ .asKnownApiLevel()
+ .getApiLevel()
+ .isLessThanOrEqualTo(options.getMinApiLevel());
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/EntryUtils.java b/src/main/java/com/android/tools/r8/utils/EntryUtils.java
index e1d28b1..c096d00 100644
--- a/src/main/java/com/android/tools/r8/utils/EntryUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/EntryUtils.java
@@ -5,11 +5,12 @@
package com.android.tools.r8.utils;
import java.util.Map.Entry;
-import java.util.function.BiFunction;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
public class EntryUtils {
- public static <K, V, R> R accept(Entry<K, V> entry, BiFunction<K, V, R> consumer) {
- return consumer.apply(entry.getKey(), entry.getValue());
+ public static <K, V> Consumer<Entry<K, V>> accept(BiConsumer<K, V> consumer) {
+ return entry -> consumer.accept(entry.getKey(), entry.getValue());
}
}
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 f3b82e4d..c836c46 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
import static com.android.tools.r8.utils.AndroidApiLevel.ANDROID_PLATFORM;
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationMode;
@@ -395,6 +396,10 @@
public boolean emitNestAnnotationsInDex =
System.getProperty("com.android.tools.r8.emitNestAnnotationsInDex") != null;
+ // Flag to allow permitted subclasses annotations in DEX. See b/231930852 for context.
+ public boolean emitPermittedSubclassesAnnotationsInDex =
+ System.getProperty("com.android.tools.r8.emitPermittedSubclassesAnnotationsInDex") != null;
+
// Contain the contents of the build properties file from the compiler command.
public DumpOptions dumpOptions;
@@ -873,35 +878,6 @@
return ImmutableSet.of();
}
- public static boolean isSystemPropertyForDevelopmentSet(String propertyName) {
- if (Version.isDevelopmentVersion()) {
- return System.getProperty(propertyName) != null;
- }
- return false;
- }
-
- public static String getSystemPropertyForDevelopmentOrDefault(
- String propertyName, String defaultValue) {
- if (Version.isDevelopmentVersion()) {
- String propertyValue = System.getProperty(propertyName);
- if (propertyValue != null) {
- return propertyValue;
- }
- }
- return defaultValue;
- }
-
- private static int parseSystemPropertyForDevelopmentOrDefault(
- String propertyName, int defaultValue) {
- if (Version.isDevelopmentVersion()) {
- String propertyValue = System.getProperty(propertyName);
- if (propertyValue != null) {
- return Integer.parseInt(propertyValue);
- }
- }
- return defaultValue;
- }
-
public static class InvalidParameterAnnotationInfo {
final DexMethod method;
@@ -1416,7 +1392,7 @@
public class InlinerOptions {
public boolean enableInlining =
- !isSystemPropertyForDevelopmentSet("com.android.tools.r8.disableinlining");
+ !parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.disableinlining", false);
// This defines the limit of instructions in the inlinee
public int simpleInliningInstructionLimit =
@@ -2030,6 +2006,10 @@
return !isDesugaring();
}
+ public boolean canUseSealedClasses() {
+ return !isDesugaring() || emitPermittedSubclassesAnnotationsInDex;
+ }
+
public boolean canLeaveStaticInterfaceMethodInvokes() {
return !isDesugaring() || hasMinApi(AndroidApiLevel.L);
}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index fb61406..389fd36 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.debuginfo.DebugRepresentation.DebugRepresentationPredicate;
import com.android.tools.r8.dex.code.DexInstruction;
import com.android.tools.r8.errors.Unreachable;
@@ -74,10 +75,9 @@
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
-import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
@@ -354,12 +354,10 @@
private interface PcBasedDebugInfoRecorder {
/** Callback to record a code object with a given max instruction PC and parameter count. */
- void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount);
+ void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc);
/** Callback to record a code object with only a single "line". */
- void recordSingleLineFor(DexCode code, int parameterCount);
-
- void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc);
+ void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc);
/**
* Install the correct debug info objects.
@@ -372,11 +370,33 @@
private static class Pc2PcMappingSupport implements PcBasedDebugInfoRecorder {
- // Some DEX VMs require matching parameter count in methods and debug info.
- // Record the max pc for each parameter count so we can share the param count objects.
- private Int2IntMap paramToMaxPc = new Int2IntOpenHashMap();
+ private static class UpdateInfo {
+ final DexCode code;
+ final int paramCount;
+ final int maxEncodingPc;
- private final List<Pair<Integer, DexCode>> codesToUpdate = new ArrayList<>();
+ public UpdateInfo(DexCode code, int paramCount, int maxEncodingPc) {
+ this.code = code;
+ this.paramCount = paramCount;
+ this.maxEncodingPc = maxEncodingPc;
+ }
+
+ // Used as key when building the shared debug info map.
+ // Only param and max-pc are part of the key.
+
+ @Override
+ public boolean equals(Object o) {
+ UpdateInfo that = (UpdateInfo) o;
+ return paramCount == that.paramCount && maxEncodingPc == that.maxEncodingPc;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(paramCount, maxEncodingPc);
+ }
+ }
+
+ private final List<UpdateInfo> codesToUpdate = new ArrayList<>();
// We can only drop single-line debug info if it is OK to lose the source-file info.
// This list is null if we must retain single-line entries.
@@ -387,46 +407,33 @@
}
@Override
- public void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount) {
- codesToUpdate.add(new Pair<>(parameterCount, code));
- int existing = paramToMaxPc.getOrDefault(parameterCount, -1);
- if (existing < lastInstructionPc) {
- paramToMaxPc.put(parameterCount, lastInstructionPc);
- }
+ public void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc) {
+ assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(code, maxEncodingPc);
+ codesToUpdate.add(new UpdateInfo(code, parameterCount, maxEncodingPc));
}
@Override
- public void recordSingleLineFor(DexCode code, int parameterCount) {
+ public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
if (singleLineCodesToClear != null) {
singleLineCodesToClear.add(code);
return;
}
- int lastInstructionPc = ArrayUtils.last(code.instructions).getOffset();
- recordPcMappingFor(code, lastInstructionPc, parameterCount);
- }
-
- @Override
- public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) {
- if (singleLineCodesToClear != null) {
- singleLineCodesToClear.add(code);
- return;
- }
- recordPcMappingFor(code, lastInstructionPc, parameterCount);
+ recordPcMappingFor(code, parameterCount, maxEncodingPc);
}
@Override
public void updateDebugInfoInCodeObjects() {
- Int2ReferenceMap<DexDebugInfo> debugInfos =
- new Int2ReferenceOpenHashMap<>(paramToMaxPc.size());
+ Object2ReferenceMap<UpdateInfo, DexDebugInfo> debugInfos =
+ new Object2ReferenceOpenHashMap<>();
codesToUpdate.forEach(
entry -> {
- int parameterCount = entry.getFirst();
- DexCode code = entry.getSecond();
+ assert DebugRepresentation.verifyLastExecutableInstructionWithinBound(
+ entry.code, entry.maxEncodingPc);
DexDebugInfo debugInfo =
debugInfos.computeIfAbsent(
- parameterCount,
- key -> buildPc2PcDebugInfo(paramToMaxPc.get(key), parameterCount));
- code.setDebugInfo(debugInfo);
+ entry, key -> buildPc2PcDebugInfo(key.maxEncodingPc, key.paramCount));
+ assert debugInfo.asPcBasedInfo().getMaxPc() == entry.maxEncodingPc;
+ entry.code.setDebugInfo(debugInfo);
});
if (singleLineCodesToClear != null) {
singleLineCodesToClear.forEach(c -> c.setDebugInfo(null));
@@ -441,23 +448,18 @@
private static class NativePcSupport implements PcBasedDebugInfoRecorder {
@Override
- public void recordPcMappingFor(DexCode code, int lastInstructionPc, int length) {
+ public void recordPcMappingFor(DexCode code, int length, int maxEncodingPc) {
// Strip the info in full as the runtime will emit the PC directly.
code.setDebugInfo(null);
}
@Override
- public void recordSingleLineFor(DexCode code, int parameterCount) {
+ public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
// Strip the info at once as it does not conflict with any PC mapping update.
code.setDebugInfo(null);
}
@Override
- public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) {
- recordSingleLineFor(code, parameterCount);
- }
-
- @Override
public void updateDebugInfoInCodeObjects() {
// Already null out the info so nothing to do.
}
@@ -476,6 +478,7 @@
ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
Map<DexMethod, OutlineFixupBuilder> outlinesToFix = new IdentityHashMap<>();
+ Map<DexType, String> prunedInlinedClasses = new IdentityHashMap<>();
PcBasedDebugInfoRecorder pcBasedDebugInfo =
appView.options().canUseNativeDexPcInsteadOfDebugInfo()
@@ -563,21 +566,23 @@
kotlinRemapper.currentMethod = definition;
List<MappedPosition> mappedPositions;
Code code = definition.getCode();
- boolean canUseDexPc =
- methods.size() == 1 && representation.useDexPcEncoding(clazz, definition);
+ int pcEncodingCutoff =
+ methods.size() == 1 ? representation.getDexPcEncodingCutoff(clazz, definition) : -1;
+ boolean canUseDexPc = pcEncodingCutoff > 0;
if (code != null) {
- if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
+ if (code.isDexCode()
+ && mustHaveResidualDebugInfo(code.asDexCode(), appView.options())) {
if (canUseDexPc) {
mappedPositions =
optimizeDexCodePositionsForPc(
- definition, appView, kotlinRemapper, pcBasedDebugInfo);
+ definition, appView, kotlinRemapper, pcBasedDebugInfo, pcEncodingCutoff);
} else {
mappedPositions =
optimizeDexCodePositions(
definition, appView, kotlinRemapper, identityMapping, methods.size() != 1);
}
} else if (code.isCfCode()
- && doesContainPositions(code.asCfCode())
+ && mustHaveResidualDebugInfo(code.asCfCode())
&& !appView.isCfByteCodePassThrough(definition)) {
mappedPositions = optimizeCfCodePositions(method, kotlinRemapper, appView);
} else {
@@ -607,7 +612,7 @@
&& obfuscatedNameDexString == originalMethod.name
&& originalMethod.holder == originalType) {
assert appView.options().lineNumberOptimization == LineNumberOptimization.OFF
- || !doesContainPositions(definition)
+ || hasAtMostOnePosition(definition, appView.options())
|| appView.isCfByteCodePassThrough(definition);
continue;
}
@@ -697,14 +702,15 @@
ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.computeIfAbsent();
MappedRange lastMappedRange =
getMappedRangesForPosition(
- appView.options().dexItemFactory(),
+ appView,
getOriginalMethodSignature,
classNamingBuilder,
firstPosition.method,
obfuscatedName,
obfuscatedRange,
new Range(firstPosition.originalLine, lastPosition.originalLine),
- firstPosition.caller);
+ firstPosition.caller,
+ prunedInlinedClasses);
for (MappingInformation info : methodMappingInfo) {
lastMappedRange.addMappingInformation(info, Unreachable::raise);
}
@@ -723,14 +729,15 @@
}
positionMap.put((int) line, placeHolderLineToBeFixed);
getMappedRangesForPosition(
- appView.options().dexItemFactory(),
+ appView,
getOriginalMethodSignature,
classNamingBuilder,
position.getMethod(),
obfuscatedName,
new Range(placeHolderLineToBeFixed, placeHolderLineToBeFixed),
new Range(position.getLine(), position.getLine()),
- position.getCallerPosition());
+ position.getCallerPosition(),
+ prunedInlinedClasses);
});
outlinesToFix
.computeIfAbsent(
@@ -743,7 +750,7 @@
&& definition.getCode().asDexCode().getDebugInfo()
== DexDebugInfoForSingleLineMethod.getInstance()) {
pcBasedDebugInfo.recordSingleLineFor(
- definition.getCode().asDexCode(), method.getParameters().size());
+ definition.getCode().asDexCode(), method.getParameters().size(), pcEncodingCutoff);
}
} // for each method of the group
} // for each method group, grouped by name
@@ -755,9 +762,39 @@
// Update all the debug-info objects.
pcBasedDebugInfo.updateDebugInfoInCodeObjects();
+ // Add all pruned inline classes to the mapping to recover source files.
+ List<Entry<DexType, String>> prunedEntries = new ArrayList<>(prunedInlinedClasses.entrySet());
+ prunedEntries.sort(Entry.comparingByKey());
+ prunedEntries.forEach(
+ entry -> {
+ DexType holder = entry.getKey();
+ assert appView.appInfo().definitionForWithoutExistenceAssert(holder) == null;
+ String typeName = holder.toSourceString();
+ String sourceFile = entry.getValue();
+ assert !RetraceUtils.hasPredictableSourceFileName(typeName, sourceFile);
+ classNameMapperBuilder
+ .classNamingBuilder(
+ typeName, typeName, com.android.tools.r8.position.Position.UNKNOWN)
+ .addMappingInformation(FileNameInformation.build(sourceFile), Unreachable::raise);
+ });
+
return classNameMapperBuilder.build();
}
+ private static boolean hasAtMostOnePosition(
+ DexEncodedMethod definition, InternalOptions options) {
+ if (!mustHaveResidualDebugInfo(definition, options)) {
+ return true;
+ }
+ Code code = definition.getCode();
+ if (code.isDexCode() && code.asDexCode().instructions.length == 1) {
+ // If the dex code is a single PC code then that also qualifies as having at most one
+ // position.
+ return true;
+ }
+ return false;
+ }
+
private static DexMethod getOutlineMethod(MappedPosition mappedPosition) {
if (mappedPosition.isOutline) {
return mappedPosition.method;
@@ -770,14 +807,15 @@
}
private static MappedRange getMappedRangesForPosition(
- DexItemFactory factory,
+ AppView<?> appView,
Function<DexMethod, MethodSignature> getOriginalMethodSignature,
Builder classNamingBuilder,
DexMethod method,
String obfuscatedName,
Range obfuscatedRange,
Range originalLine,
- Position caller) {
+ Position caller,
+ Map<DexType, String> prunedInlineHolder) {
MappedRange lastMappedRange =
classNamingBuilder.addMappedRange(
obfuscatedRange,
@@ -787,6 +825,13 @@
int inlineFramesCount = 0;
while (caller != null) {
inlineFramesCount += 1;
+ String prunedClassSourceFileInfo =
+ appView.getPrunedClassSourceFileInfo(method.getHolderType());
+ if (prunedClassSourceFileInfo != null) {
+ String originalValue =
+ prunedInlineHolder.put(method.getHolderType(), prunedClassSourceFileInfo);
+ assert originalValue == null || originalValue.equals(prunedClassSourceFileInfo);
+ }
lastMappedRange =
classNamingBuilder.addMappedRange(
obfuscatedRange,
@@ -798,7 +843,8 @@
RewriteFrameMappingInformation.builder()
.addCondition(
ThrowsCondition.create(
- Reference.classFromDescriptor(factory.npeDescriptor.toString())))
+ Reference.classFromDescriptor(
+ appView.dexItemFactory().npeDescriptor.toString())))
.addRewriteAction(RemoveInnerFramesAction.create(inlineFramesCount))
.build(),
Unreachable::raise);
@@ -919,13 +965,13 @@
IdentityHashMap<DexString, List<ProgramMethod>> methodsByRenamedName =
new IdentityHashMap<>(clazz.getMethodCollection().size());
for (ProgramMethod programMethod : clazz.programMethods()) {
- // Add method only if renamed, moved, or contains positions.
+ // Add method only if renamed, moved, or if it has debug info to map.
DexEncodedMethod definition = programMethod.getDefinition();
DexMethod method = programMethod.getReference();
DexString renamedName = appView.getNamingLens().lookupName(method);
if (renamedName != method.name
|| appView.graphLens().getOriginalMethodSignature(method) != method
- || doesContainPositions(definition)
+ || mustHaveResidualDebugInfo(definition, appView.options())
|| definition.isD8R8Synthesized()) {
methodsByRenamedName
.computeIfAbsent(renamedName, key -> new ArrayList<>())
@@ -935,20 +981,26 @@
return methodsByRenamedName;
}
- private static boolean doesContainPositions(DexEncodedMethod method) {
+ private static boolean mustHaveResidualDebugInfo(
+ DexEncodedMethod method, InternalOptions options) {
Code code = method.getCode();
if (code == null) {
return false;
}
if (code.isDexCode()) {
- return doesContainPositions(code.asDexCode());
+ return mustHaveResidualDebugInfo(code.asDexCode(), options);
} else if (code.isCfCode()) {
- return doesContainPositions(code.asCfCode());
+ return mustHaveResidualDebugInfo(code.asCfCode());
}
return false;
}
- public static boolean doesContainPositions(DexCode dexCode) {
+ public static boolean mustHaveResidualDebugInfo(DexCode dexCode, InternalOptions options) {
+ // All code objects must have debug info if discarding it is not allowed.
+ if (!options.allowDiscardingResidualDebugInfo()) {
+ return true;
+ }
+ // Otherwise debug info is only needed for code sequences with at least one position.
DexDebugInfo debugInfo = dexCode.getDebugInfo();
if (debugInfo == null) {
return false;
@@ -964,7 +1016,7 @@
return false;
}
- private static boolean doesContainPositions(CfCode cfCode) {
+ private static boolean mustHaveResidualDebugInfo(CfCode cfCode) {
List<CfInstruction> instructions = cfCode.getInstructions();
for (CfInstruction instruction : instructions) {
if (instruction instanceof CfPosition) {
@@ -984,10 +1036,8 @@
// Do the actual processing for each method.
DexApplication application = appView.appInfo().app();
DexCode dexCode = method.getCode().asDexCode();
- // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
- EventBasedDebugInfo debugInfo =
- DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
- assert debugInfo != null;
+ EventBasedDebugInfo debugInfo = getEventBasedDebugInfo(method, dexCode, appView);
+
List<DexDebugEvent> processedEvents = new ArrayList<>();
PositionEventEmitter positionEventEmitter =
@@ -1099,6 +1149,29 @@
return mappedPositions;
}
+ // This conversion *always* creates an event based debug info encoding as any non-info will
+ // be created as an implicit PC encoding.
+ private static EventBasedDebugInfo getEventBasedDebugInfo(
+ DexEncodedMethod method, DexCode dexCode, AppView<?> appView) {
+ // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
+ if (dexCode.getDebugInfo() == null) {
+ return createEventBasedInfoForMethodWithoutDebugInfo(method, appView.dexItemFactory());
+ }
+ assert method.getParameters().size() == dexCode.getDebugInfo().getParameterCount();
+ EventBasedDebugInfo debugInfo =
+ DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
+ assert debugInfo != null;
+ return debugInfo;
+ }
+
+ public static EventBasedDebugInfo createEventBasedInfoForMethodWithoutDebugInfo(
+ DexEncodedMethod method, DexItemFactory factory) {
+ return new EventBasedDebugInfo(
+ 0,
+ new DexString[method.getParameters().size()],
+ new DexDebugEvent[] {factory.zeroChangeDefaultEvent});
+ }
+
private static Position getPositionFromPositionState(DexDebugPositionState state) {
PositionBuilder<?, ?> positionBuilder;
if (state.getOutlineCallee() != null) {
@@ -1124,14 +1197,12 @@
DexEncodedMethod method,
AppView<?> appView,
PositionRemapper positionRemapper,
- PcBasedDebugInfoRecorder debugInfoProvider) {
+ PcBasedDebugInfoRecorder debugInfoProvider,
+ int pcEncodingCutoff) {
List<MappedPosition> mappedPositions = new ArrayList<>();
// Do the actual processing for each method.
DexCode dexCode = method.getCode().asDexCode();
- // TODO(b/213411850): Do we need to reconsider conversion here to support pc-based D8 merging?
- EventBasedDebugInfo debugInfo =
- DexDebugInfo.convertToEventBased(dexCode, appView.dexItemFactory());
- assert debugInfo != null;
+ EventBasedDebugInfo debugInfo = getEventBasedDebugInfo(method, dexCode, appView);
IntBox firstDefaultEventPc = new IntBox(-1);
BooleanBox singleOriginalLine = new BooleanBox(true);
Pair<Integer, Position> lastPosition = new Pair<>();
@@ -1183,7 +1254,7 @@
}
}
- int lastInstructionPc = ArrayUtils.last(dexCode.instructions).getOffset();
+ int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(dexCode).getOffset();
if (lastPosition.getSecond() != null) {
remapAndAddForPc(
lastPosition.getFirst(),
@@ -1193,16 +1264,15 @@
mappedPositions);
}
- // If we only have one original line we can always retrace back uniquely.
- assert !mappedPositions.isEmpty();
+ assert !mappedPositions.isEmpty() || dexCode.instructions.length == 1;
if (singleOriginalLine.isTrue()
&& lastPosition.getSecond() != null
- && !mappedPositions.get(0).isOutlineCaller()) {
+ && (mappedPositions.isEmpty() || !mappedPositions.get(0).isOutlineCaller())) {
dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
debugInfoProvider.recordSingleLineFor(
- dexCode, method.getParameters().size(), lastInstructionPc);
+ dexCode, method.getParameters().size(), pcEncodingCutoff);
} else {
- debugInfoProvider.recordPcMappingFor(dexCode, lastInstructionPc, debugInfo.parameters.length);
+ debugInfoProvider.recordPcMappingFor(dexCode, debugInfo.parameters.length, pcEncodingCutoff);
}
return mappedPositions;
}
diff --git a/src/main/java/com/android/tools/r8/utils/LongInterval.java b/src/main/java/com/android/tools/r8/utils/LongInterval.java
index 7e721db..a05281a 100644
--- a/src/main/java/com/android/tools/r8/utils/LongInterval.java
+++ b/src/main/java/com/android/tools/r8/utils/LongInterval.java
@@ -36,6 +36,10 @@
return min == max;
}
+ public boolean isSingleValue(int value) {
+ return isSingleValue() && getSingleValue() == value;
+ }
+
public long getSingleValue() {
assert isSingleValue();
return min;
diff --git a/src/main/java/com/android/tools/r8/utils/QuadConsumer.java b/src/main/java/com/android/tools/r8/utils/QuadConsumer.java
new file mode 100644
index 0000000..3249ce0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/QuadConsumer.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+public interface QuadConsumer<S, T, U, V> {
+
+ void accept(S s, T t, U u, V v);
+}
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index b53baff..59011c5 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -286,6 +286,14 @@
return codePoint == BOM;
}
+ public static boolean isFalsy(String string) {
+ return string.equals("0") || string.toLowerCase().equals("false");
+ }
+
+ public static boolean isTruthy(String string) {
+ return string.equals("1") || string.toLowerCase().equals("true");
+ }
+
public static boolean isWhitespace(int codePoint) {
return Character.isWhitespace(codePoint) || isBOM(codePoint);
}
@@ -347,6 +355,10 @@
return subject.replaceAll(Pattern.quote(target), Matcher.quoteReplacement(replacement));
}
+ public static String quote(String string) {
+ return "\"" + string + "\"";
+ }
+
public static String stacktraceAsString(Throwable throwable) {
StringWriter sw = new StringWriter();
throwable.printStackTrace(new PrintWriter(sw));
diff --git a/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java b/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java
new file mode 100644
index 0000000..d8d4a38
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import static com.google.common.base.Predicates.alwaysTrue;
+
+import com.android.tools.r8.Version;
+import java.util.function.Predicate;
+
+public class SystemPropertyUtils {
+
+ public static String getSystemPropertyForDevelopment(String propertyName) {
+ return Version.isDevelopmentVersion() ? System.getProperty(propertyName) : null;
+ }
+
+ public static String getSystemPropertyForDevelopmentOrDefault(
+ String propertyName, String defaultValue) {
+ return Version.isDevelopmentVersion()
+ ? System.getProperty(propertyName, defaultValue)
+ : defaultValue;
+ }
+
+ public static boolean hasSystemPropertyThatMatches(
+ String propertyName, Predicate<String> predicate) {
+ String propertyValue = System.getProperty(propertyName);
+ return propertyValue != null && predicate.test(propertyValue);
+ }
+
+ public static boolean hasSystemPropertyForDevelopmentThatMatches(
+ String propertyName, Predicate<String> predicate) {
+ String propertyValue = getSystemPropertyForDevelopment(propertyName);
+ return propertyValue != null && predicate.test(propertyValue);
+ }
+
+ public static boolean isSystemPropertySet(String propertyName) {
+ return hasSystemPropertyThatMatches(propertyName, alwaysTrue());
+ }
+
+ public static boolean isSystemPropertyForDevelopmentSet(String propertyName) {
+ return hasSystemPropertyForDevelopmentThatMatches(propertyName, alwaysTrue());
+ }
+
+ public static boolean parseSystemPropertyOrDefault(String propertyName, boolean defaultValue) {
+ return internalParseSystemPropertyForDevelopmentOrDefault(
+ propertyName, System.getProperty(propertyName), defaultValue);
+ }
+
+ public static boolean parseSystemPropertyForDevelopmentOrDefault(
+ String propertyName, boolean defaultValue) {
+ return internalParseSystemPropertyForDevelopmentOrDefault(
+ propertyName, getSystemPropertyForDevelopment(propertyName), defaultValue);
+ }
+
+ private static boolean internalParseSystemPropertyForDevelopmentOrDefault(
+ String propertyName, String propertyValue, boolean defaultValue) {
+ if (propertyValue == null) {
+ return defaultValue;
+ }
+ if (StringUtils.isFalsy(propertyValue)) {
+ return false;
+ }
+ if (StringUtils.isTruthy(propertyValue)) {
+ return true;
+ }
+ throw new IllegalArgumentException(
+ "Expected value of " + propertyName + " to be a boolean, but was: " + propertyValue);
+ }
+
+ public static int parseSystemPropertyOrDefault(String propertyName, int defaultValue) {
+ String propertyValue = System.getProperty(propertyName);
+ return propertyValue != null ? Integer.parseInt(propertyValue) : defaultValue;
+ }
+
+ public static int parseSystemPropertyForDevelopmentOrDefault(
+ String propertyName, int defaultValue) {
+ String propertyValue = getSystemPropertyForDevelopment(propertyName);
+ return propertyValue != null ? Integer.parseInt(propertyValue) : defaultValue;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 2e7cd20..480f7b4 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.QuadConsumer;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
@@ -48,6 +49,36 @@
Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
}
+ protected void ensureCustomCheck(
+ QuadConsumer<ProcessResult, ProcessResult, ProcessResult, ProcessResult> checker,
+ String main,
+ AndroidApiLevel apiLevel,
+ List<String> args,
+ byte[]... classes)
+ throws Exception {
+ AndroidApp app = buildAndroidApp(classes);
+ Consumer<InternalOptions> setMinApiLevel = o -> o.setMinApiLevel(apiLevel);
+ ProcessResult javaResult = runOnJavaRaw(main, Arrays.asList(classes), args);
+ Consumer<ArtCommandBuilder> cmdBuilder =
+ builder -> {
+ for (String arg : args) {
+ builder.appendProgramArgument(arg);
+ }
+ };
+ ProcessResult d8Result =
+ runOnArtRaw(compileWithD8(app, setMinApiLevel), main, cmdBuilder, null);
+ ProcessResult r8Result =
+ runOnArtRaw(compileWithR8(app, setMinApiLevel), main, cmdBuilder, null);
+ ProcessResult r8ShakenResult =
+ runOnArtRaw(
+ compileWithR8(
+ app, keepMainProguardConfiguration(main) + "-dontobfuscate\n", setMinApiLevel),
+ main,
+ cmdBuilder,
+ null);
+ checker.accept(javaResult, d8Result, r8Result, r8ShakenResult);
+ }
+
protected void ensureSameOutput(String main, byte[]... classes) throws Exception {
AndroidApp app = buildAndroidApp(classes);
ensureSameOutput(main, app, false, classes);
diff --git a/src/test/java/com/android/tools/r8/CheckEnumUnboxed.java b/src/test/java/com/android/tools/r8/CheckEnumUnboxed.java
new file mode 100644
index 0000000..11d8719
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/CheckEnumUnboxed.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface CheckEnumUnboxed {}
diff --git a/src/test/java/com/android/tools/r8/L8TestBuilder.java b/src/test/java/com/android/tools/r8/L8TestBuilder.java
index 7b84c77..c9f0ee8 100644
--- a/src/test/java/com/android/tools/r8/L8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/L8TestBuilder.java
@@ -35,13 +35,10 @@
private CompilationMode mode = CompilationMode.RELEASE;
private String generatedKeepRules = null;
private List<String> keepRules = new ArrayList<>();
- private List<Path> additionalProgramFiles = new ArrayList<>();
- private List<byte[]> additionalProgramClassFileData = new ArrayList<>();
+ private List<Path> programFiles = new ArrayList<>();
+ private List<byte[]> programClassFileData = new ArrayList<>();
private Consumer<InternalOptions> optionsModifier = ConsumerUtils.emptyConsumer();
- private Path desugarJDKLibs = ToolHelper.getDesugarJDKLibs();
- private Path customConversions = null;
- private StringResource desugaredLibrarySpecification =
- StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting());
+ private StringResource desugaredLibrarySpecification = null;
private List<Path> libraryFiles = new ArrayList<>();
private ProgramConsumer programConsumer;
private boolean finalPrefixVerification = true;
@@ -61,13 +58,18 @@
return this;
}
+ public L8TestBuilder addProgramFiles(Path... programFiles) {
+ this.programFiles.addAll(Arrays.asList(programFiles));
+ return this;
+ }
+
public L8TestBuilder addProgramFiles(Collection<Path> programFiles) {
- this.additionalProgramFiles.addAll(programFiles);
+ this.programFiles.addAll(programFiles);
return this;
}
public L8TestBuilder addProgramClassFileData(byte[]... classes) {
- this.additionalProgramClassFileData.addAll(Arrays.asList(classes));
+ this.programClassFileData.addAll(Arrays.asList(classes));
return this;
}
@@ -134,42 +136,16 @@
return this;
}
- public L8TestBuilder setDesugarJDKLibs(Path desugarJDKLibs) {
- assert desugarJDKLibs != null : "Use noDefaultDesugarJDKLibs to clear the default.";
- this.desugarJDKLibs = desugarJDKLibs;
- return this;
- }
-
- public L8TestBuilder noDefaultDesugarJDKLibs() {
- this.desugarJDKLibs = null;
- return this;
- }
-
public L8TestBuilder setProgramConsumer(ProgramConsumer programConsumer) {
this.programConsumer = programConsumer;
return this;
}
- public L8TestBuilder setDesugarJDKLibsCustomConversions(Path desugarJDKLibsConfiguration) {
- this.customConversions = desugarJDKLibsConfiguration;
- return this;
- }
-
- public L8TestBuilder setDesugaredLibraryConfiguration(Path path) {
+ public L8TestBuilder setDesugaredLibrarySpecification(Path path) {
this.desugaredLibrarySpecification = StringResource.fromFile(path);
return this;
}
- public L8TestBuilder setDesugaredLibraryConfiguration(StringResource configuration) {
- this.desugaredLibrarySpecification = configuration;
- return this;
- }
-
- public L8TestBuilder setDisableL8AnnotationRemoval(boolean disableL8AnnotationRemoval) {
- return addOptionsModifier(
- options -> options.disableL8AnnotationRemoval = disableL8AnnotationRemoval);
- }
-
private ProgramConsumer computeProgramConsumer(AndroidAppConsumers sink) {
if (programConsumer != null) {
return programConsumer;
@@ -185,7 +161,7 @@
AndroidAppConsumers sink = new AndroidAppConsumers();
L8Command.Builder l8Builder =
L8Command.builder(state.getDiagnosticsHandler())
- .addProgramFiles(getProgramFiles())
+ .addProgramFiles(programFiles)
.addLibraryFiles(getLibraryFiles())
.setMode(mode)
.setIncludeClassesChecksum(true)
@@ -234,20 +210,8 @@
|| clazz.getFinalName().startsWith("java.")))));
}
- private Collection<Path> getProgramFiles() {
- ImmutableList.Builder<Path> builder = ImmutableList.builder();
- if (desugarJDKLibs != null) {
- builder.add(desugarJDKLibs);
- }
- if (customConversions != null) {
- builder.add(customConversions);
- }
- return builder.addAll(additionalProgramFiles).build();
- }
-
private L8Command.Builder addProgramClassFileData(L8Command.Builder builder) {
- additionalProgramClassFileData.forEach(
- data -> builder.addClassProgramData(data, Origin.unknown()));
+ programClassFileData.forEach(data -> builder.addClassProgramData(data, Origin.unknown()));
return builder;
}
diff --git a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
index 52cc764..1052657 100644
--- a/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
+++ b/src/test/java/com/android/tools/r8/LibraryDesugaringTestConfiguration.java
@@ -4,146 +4,40 @@
package com.android.tools.r8;
-import static com.android.tools.r8.LibraryDesugaringTestConfiguration.Configuration.DEFAULT;
-import static junit.framework.TestCase.assertNotNull;
-import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.ExecutionException;
public class LibraryDesugaringTestConfiguration {
- private static final String RELEASES_DIR = "third_party/openjdk/desugar_jdk_libs_releases/";
-
- public enum Configuration {
- DEFAULT(
- ToolHelper.getDesugarJDKLibs(),
- ToolHelper.DESUGAR_LIB_CONVERSIONS,
- ToolHelper.getDesugarLibJsonForTesting()),
- DEFAULT_JDK11(
- Paths.get("third_party/openjdk/desugar_jdk_libs_11/desugar_jdk_libs.jar"),
- ToolHelper.DESUGAR_LIB_CONVERSIONS,
- Paths.get("src/library_desugar/jdk11/desugar_jdk_libs.json")),
- RELEASED_1_0_9("1.0.9"),
- RELEASED_1_0_10("1.0.10"),
- RELEASED_1_1_0("1.1.0"),
- RELEASED_1_1_1("1.1.1"),
- RELEASED_1_1_5("1.1.5");
-
- private final Path desugarJdkLibs;
- private final Path customConversions;
- private final Path configuration;
-
- Configuration(Path desugarJdkLibs, Path customConversions, Path configuration) {
- this.desugarJdkLibs = desugarJdkLibs;
- this.customConversions = customConversions;
- this.configuration = configuration;
- }
-
- Configuration(String version) {
- this(
- Paths.get(RELEASES_DIR, version, "desugar_jdk_libs.jar"),
- Paths.get(RELEASES_DIR, version, "desugar_jdk_libs_configuration.jar"),
- Paths.get(RELEASES_DIR, version, "desugar.json"));
- }
-
- public static List<Configuration> getReleased() {
- return ImmutableList.of(
- RELEASED_1_0_9, RELEASED_1_0_10, RELEASED_1_1_0, RELEASED_1_1_1, RELEASED_1_1_5);
- }
- }
-
- private final AndroidApiLevel minApiLevel;
- private final Path desugarJdkLibs;
- private final Path customConversions;
private final List<StringResource> desugaredLibrarySpecificationResources;
- private final boolean withKeepRuleConsumer;
private final KeepRuleConsumer keepRuleConsumer;
- private final CompilationMode mode;
- private final boolean addRunClassPath;
public static final LibraryDesugaringTestConfiguration DISABLED =
new LibraryDesugaringTestConfiguration();
private LibraryDesugaringTestConfiguration() {
- this.minApiLevel = null;
- this.desugarJdkLibs = null;
- this.customConversions = null;
this.keepRuleConsumer = null;
- this.withKeepRuleConsumer = false;
this.desugaredLibrarySpecificationResources = null;
- this.mode = null;
- this.addRunClassPath = false;
}
private LibraryDesugaringTestConfiguration(
- AndroidApiLevel minApiLevel,
- Path desugarJdkLibs,
- Path customConversions,
List<StringResource> desugaredLibrarySpecificationResources,
- boolean withKeepRuleConsumer,
- KeepRuleConsumer keepRuleConsumer,
- CompilationMode mode,
- boolean addRunClassPath) {
- this.minApiLevel = minApiLevel;
- this.desugarJdkLibs = desugarJdkLibs;
- this.customConversions = customConversions;
+ KeepRuleConsumer keepRuleConsumer) {
this.desugaredLibrarySpecificationResources = desugaredLibrarySpecificationResources;
- this.withKeepRuleConsumer = withKeepRuleConsumer;
this.keepRuleConsumer = keepRuleConsumer;
- this.mode = mode;
- this.addRunClassPath = addRunClassPath;
}
public static class Builder {
- AndroidApiLevel minApiLevel;
- private Path desugarJdkLibs;
- private Path customConversions;
private final List<StringResource> desugaredLibrarySpecificationResources = new ArrayList<>();
- boolean withKeepRuleConsumer = false;
KeepRuleConsumer keepRuleConsumer;
- private CompilationMode mode = CompilationMode.DEBUG;
- boolean addRunClassPath = true;
-
private Builder() {}
- public Builder setMinApi(AndroidApiLevel minApiLevel) {
- this.minApiLevel = minApiLevel;
- return this;
- }
-
- public Builder setConfiguration(Configuration configuration) {
- desugarJdkLibs = configuration.desugarJdkLibs;
- customConversions = configuration.customConversions;
- desugaredLibrarySpecificationResources.clear();
- desugaredLibrarySpecificationResources.add(
- StringResource.fromFile(configuration.configuration));
- return this;
- }
-
- public Builder withKeepRuleConsumer() {
- withKeepRuleConsumer = true;
- return this;
- }
-
public Builder setKeepRuleConsumer(KeepRuleConsumer keepRuleConsumer) {
- withKeepRuleConsumer = false;
- if (keepRuleConsumer == null) {
- this.keepRuleConsumer = null;
- } else {
- this.keepRuleConsumer = keepRuleConsumer;
- }
+ this.keepRuleConsumer = keepRuleConsumer;
return this;
}
@@ -152,33 +46,10 @@
return this;
}
- public Builder setMode(CompilationMode mode) {
- this.mode = mode;
- return this;
- }
-
- public Builder dontAddRunClasspath() {
- addRunClassPath = false;
- return this;
- }
-
public LibraryDesugaringTestConfiguration build() {
- if (desugaredLibrarySpecificationResources.isEmpty()) {
- desugaredLibrarySpecificationResources.add(
- StringResource.fromFile(ToolHelper.getDesugarLibJsonForTesting()));
- }
- if (withKeepRuleConsumer) {
- this.keepRuleConsumer = createKeepRuleConsumer(minApiLevel);
- }
+ assert !desugaredLibrarySpecificationResources.isEmpty();
return new LibraryDesugaringTestConfiguration(
- minApiLevel,
- desugarJdkLibs != null ? desugarJdkLibs : DEFAULT.desugarJdkLibs,
- customConversions != null ? customConversions : DEFAULT.customConversions,
- desugaredLibrarySpecificationResources,
- withKeepRuleConsumer,
- keepRuleConsumer,
- mode,
- addRunClassPath);
+ desugaredLibrarySpecificationResources, keepRuleConsumer);
}
}
@@ -186,18 +57,16 @@
return new Builder();
}
- public static LibraryDesugaringTestConfiguration forApiLevel(AndroidApiLevel apiLevel) {
- return LibraryDesugaringTestConfiguration.builder().setMinApi(apiLevel).build();
+ public static LibraryDesugaringTestConfiguration forSpecification(Path specification) {
+ return LibraryDesugaringTestConfiguration.builder()
+ .addDesugaredLibraryConfiguration(StringResource.fromFile(specification))
+ .build();
}
public boolean isEnabled() {
return this != DISABLED;
}
- public boolean isAddRunClassPath() {
- return addRunClassPath;
- }
-
public void configure(D8Command.Builder builder) {
if (!isEnabled()) {
return;
@@ -218,51 +87,6 @@
desugaredLibrarySpecificationResources.forEach(builder::addDesugaredLibraryConfiguration);
}
- public Path buildDesugaredLibrary(TestState state) {
- String generatedKeepRules = null;
- if (withKeepRuleConsumer) {
- if (keepRuleConsumer instanceof PresentKeepRuleConsumer) {
- generatedKeepRules = keepRuleConsumer.get();
- assertNotNull(generatedKeepRules);
- } else {
- assertThat(keepRuleConsumer, instanceOf(AbsentKeepRuleConsumer.class));
- }
- }
- String finalGeneratedKeepRules = generatedKeepRules;
- try {
- assert desugaredLibrarySpecificationResources.size() == 1 : "There can be only one";
- return L8TestBuilder.create(minApiLevel, Backend.DEX, state)
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
- .setDesugarJDKLibs(desugarJdkLibs)
- .setDesugarJDKLibsCustomConversions(customConversions)
- .setDesugaredLibraryConfiguration(desugaredLibrarySpecificationResources.get(0))
- .applyIf(
- mode == CompilationMode.RELEASE,
- builder -> {
- if (finalGeneratedKeepRules != null && !finalGeneratedKeepRules.trim().isEmpty()) {
- builder.addGeneratedKeepRules(finalGeneratedKeepRules);
- }
- },
- L8TestBuilder::setDebug)
- .compile()
- .writeToZip();
- } catch (CompilationFailedException | ExecutionException | IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- public KeepRuleConsumer getKeepRuleConsumer() {
- return keepRuleConsumer;
- }
-
- public static KeepRuleConsumer createKeepRuleConsumer(TestParameters parameters) {
- return createKeepRuleConsumer(parameters.getApiLevel());
- }
-
- private static KeepRuleConsumer createKeepRuleConsumer(AndroidApiLevel apiLevel) {
- return new PresentKeepRuleConsumer();
- }
-
public static class PresentKeepRuleConsumer implements KeepRuleConsumer {
StringBuilder stringBuilder = new StringBuilder();
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index ff90c43..ebe8352 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -10,12 +10,12 @@
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.benchmarks.BenchmarkResults;
-import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
import com.android.tools.r8.dexsplitter.SplitterTestBase.RunInterface;
import com.android.tools.r8.dexsplitter.SplitterTestBase.SplitRunner;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.CheckEnumUnboxedRule;
import com.android.tools.r8.shaking.CollectingGraphConsumer;
import com.android.tools.r8.shaking.KeepUnusedReturnValueRule;
import com.android.tools.r8.shaking.NoFieldTypeStrengtheningRule;
@@ -28,7 +28,6 @@
import com.android.tools.r8.shaking.NoVerticalClassMergingRule;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
-import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -500,6 +499,12 @@
return addOptionsModification(options -> options.testing.allowInliningOfSynthetics = false);
}
+ public T enableCheckEnumUnboxedAnnotations() {
+ return addCheckEnumUnboxedAnnotation()
+ .addInternalMatchInterfaceRule(CheckEnumUnboxedRule.RULE_NAME, CheckEnumUnboxed.class)
+ .enableExperimentalCheckEnumUnboxed();
+ }
+
public T enableKeepUnusedReturnValueAnnotations() {
return addKeepUnusedReturnValueAnnotation()
.addInternalMatchAnnotationOnMethodRule(
@@ -657,8 +662,23 @@
return self();
}
+ public T enableExperimentalCheckEnumUnboxed() {
+ builder.setEnableExperimentalCheckEnumUnboxed();
+ return self();
+ }
+
+ public T enableExperimentalConvertCheckNotNull() {
+ builder.setEnableExperimentalConvertCheckNotNull();
+ return self();
+ }
+
+ public T enableExperimentalWhyAreYouNotInlining() {
+ builder.setEnableExperimentalWhyAreYouNotInlining();
+ return self();
+ }
+
public T enableProguardTestOptions() {
- builder.allowTestProguardOptions();
+ builder.setEnableTestProguardOptions();
return self();
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 48f9fc5..4d557ea 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -198,10 +198,6 @@
public RR run(TestRuntime runtime, String mainClass, String... args)
throws ExecutionException, IOException {
assert getBackend() == runtime.getBackend();
- if (libraryDesugaringTestConfiguration.isEnabled()
- && libraryDesugaringTestConfiguration.isAddRunClassPath()) {
- additionalRunClassPath.add(libraryDesugaringTestConfiguration.buildDesugaredLibrary(state));
- }
ClassSubject mainClassSubject = inspector().clazz(mainClass);
if (!mainClassSubject.isPresent()) {
for (Path classpathFile : additionalRunClassPath) {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index a5c5302..58e5901 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -436,6 +436,10 @@
return addTestingAnnotation(AssumeNoSideEffects.class);
}
+ public final T addCheckEnumUnboxedAnnotation() {
+ return addTestingAnnotation(CheckEnumUnboxed.class);
+ }
+
public final T addConstantArgumentAnnotations() {
return addTestingAnnotation(KeepConstantArguments.class);
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e7c64cd..eb7df8c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -184,28 +184,13 @@
OPEN_JDK_DIR + "desugar_jdk_libs_releases/";
public static final Path DESUGARED_JDK_8_LIB_JAR =
Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs/desugar_jdk_libs.jar");
- public static final Path UNDESUGARED_JDK_11_LIB_JAR =
- DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
- Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar"));
+ public static final Path DESUGARED_JDK_11_LIB_JAR =
+ Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar");
- public static Path getDesugarJDKLibs() {
- return DesugaredLibraryJDK11Undesugarer.undesugaredJar();
- }
-
- public static Path getDesugarJDKLibsBazelGeneratedFile() {
- String property = System.getProperty("desugar_jdk_libs");
- if (property == null) {
- return DESUGARED_JDK_8_LIB_JAR;
- }
- return Paths.get(property);
- }
-
- private static String getDesugarLibraryJsonDir() {
- return System.getProperty("desugar_jdk_json_dir", "src/library_desugar");
- }
-
- public static Path getDesugarLibJsonForTesting() {
- return Paths.get(getDesugarLibraryJsonDir(), "desugar_jdk_libs.json");
+ public static Path getUndesugaredJdk11LibJarForTesting() {
+ return DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
+ Paths.get("build/libs"),
+ Paths.get(OPEN_JDK_DIR + "desugar_jdk_libs_11/desugar_jdk_libs.jar"));
}
public static boolean isLocalDevelopment() {
@@ -2227,7 +2212,7 @@
}
public static R8Command.Builder allowTestProguardOptions(R8Command.Builder builder) {
- builder.allowTestProguardOptions();
+ builder.setEnableTestProguardOptions();
return builder;
}
diff --git a/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
new file mode 100644
index 0000000..db640a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/androidapi/GenerateCovariantReturnTypeMethodsTest.java
@@ -0,0 +1,230 @@
+// Copyright (c) 2022, 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.androidapi;
+
+import static com.android.tools.r8.apimodel.JavaSourceCodePrinter.Type.fromType;
+import static com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer.isCovariantReturnTypeAnnotation;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter.JavaSourceCodeMethodPrinter;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter.KnownType;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter.MethodParameter;
+import com.android.tools.r8.apimodel.JavaSourceCodePrinter.ParameterizedType;
+import com.android.tools.r8.cfmethodgeneration.MethodGenerationBase;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.EntryUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class GenerateCovariantReturnTypeMethodsTest extends TestBase {
+
+ private static final String CLASS_NAME = "CovariantReturnTypeMethods";
+ private static final String PACKAGE_NAME = "com.android.tools.r8.androidapi";
+ // When updating to support a new api level build libcore in aosp and update the cloud dependency.
+ private static final Path PATH_TO_CORE_JAR =
+ Paths.get(ToolHelper.THIRD_PARTY_DIR, "android_jar", "libcore_latest", "core-oj.jar");
+ private static final Path DESTINATION_FILE =
+ Paths.get(ToolHelper.SOURCE_DIR)
+ .resolve(PACKAGE_NAME.replace('.', '/'))
+ .resolve(CLASS_NAME + ".java");
+ private static final AndroidApiLevel GENERATED_FOR_API_LEVEL = AndroidApiLevel.T;
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void testLibCoreNeedsUpgrading() {
+ assertEquals(GENERATED_FOR_API_LEVEL, AndroidApiLevel.LATEST);
+ }
+
+ @Test
+ public void testCanFindAnnotatedMethodsInJar() throws Exception {
+ CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
+ // These assertions are here to ensure we produce a sane result.
+ assertEquals(51, covariantMethodsInJar.methodReferenceMap.size());
+ }
+
+ @Test
+ public void testGeneratedCodeUpToDate() throws Exception {
+ assertEquals(FileUtils.readTextFile(DESTINATION_FILE, StandardCharsets.UTF_8), generateCode());
+ }
+
+ public static String generateCode() throws Exception {
+ CovariantMethodsInJarResult covariantMethodsInJar = CovariantMethodsInJarResult.create();
+ Map<MethodReference, List<MethodReference>> methodReferenceMap =
+ covariantMethodsInJar.methodReferenceMap;
+ List<Entry<MethodReference, List<MethodReference>>> entries =
+ new ArrayList<>(methodReferenceMap.entrySet());
+ entries.sort(Entry.comparingByKey(MethodReferenceUtils.getMethodReferenceComparator()));
+ JavaSourceCodePrinter printer =
+ JavaSourceCodePrinter.builder()
+ .setHeader(
+ MethodGenerationBase.getHeaderString(
+ 2022, GenerateCovariantReturnTypeMethodsTest.class.getSimpleName()))
+ .setPackageName(PACKAGE_NAME)
+ .setClassName(CLASS_NAME)
+ .build();
+ String javaSourceCode =
+ printer
+ .addMethod(
+ "public static",
+ null,
+ "registerMethodsWithCovariantReturnType",
+ ImmutableList.of(
+ MethodParameter.build(fromType(KnownType.DexItemFactory), "factory"),
+ MethodParameter.build(
+ ParameterizedType.fromType(
+ KnownType.Consumer, fromType(KnownType.DexMethod)),
+ "consumer")),
+ methodPrinter ->
+ entries.forEach(
+ EntryUtils.accept(
+ (ignored, covariations) ->
+ covariations.forEach(
+ covariant ->
+ registerCovariantMethod(methodPrinter, covariant)))))
+ .toString();
+ Path tempFile = Files.createTempFile("output-", ".java");
+ Files.write(tempFile, javaSourceCode.getBytes(StandardCharsets.UTF_8));
+ return MethodGenerationBase.formatRawOutput(tempFile);
+ }
+
+ private static void registerCovariantMethod(
+ JavaSourceCodeMethodPrinter methodPrinter, MethodReference covariant) {
+ methodPrinter
+ .addInstanceMethodCall(
+ "consumer",
+ "accept",
+ () ->
+ methodPrinter.addInstanceMethodCall(
+ "factory",
+ "createMethod",
+ callCreateType(methodPrinter, covariant.getHolderClass().getDescriptor()),
+ callCreateProto(
+ methodPrinter,
+ covariant.getReturnType().getDescriptor(),
+ covariant.getFormalTypes().stream()
+ .map(TypeReference::getDescriptor)
+ .collect(Collectors.toList())),
+ methodPrinter.literal(covariant.getMethodName())))
+ .addSemicolon()
+ .newLine();
+ }
+
+ private static Action callCreateType(
+ JavaSourceCodeMethodPrinter methodPrinter, String descriptor) {
+ return () ->
+ methodPrinter.addInstanceMethodCall(
+ "factory", "createType", methodPrinter.literal(descriptor));
+ }
+
+ private static Action callCreateProto(
+ JavaSourceCodeMethodPrinter methodPrinter,
+ String returnTypeDescriptor,
+ Collection<String> args) {
+ List<Action> actionList = new ArrayList<>();
+ actionList.add(callCreateType(methodPrinter, returnTypeDescriptor));
+ for (String arg : args) {
+ actionList.add(callCreateType(methodPrinter, arg));
+ }
+ return () -> methodPrinter.addInstanceMethodCall("factory", "createProto", actionList);
+ }
+
+ public static void main(String[] args) throws Exception {
+ Files.write(DESTINATION_FILE, generateCode().getBytes(StandardCharsets.UTF_8));
+ }
+
+ public static class CovariantMethodsInJarResult {
+ private final Map<MethodReference, List<MethodReference>> methodReferenceMap;
+
+ private CovariantMethodsInJarResult(
+ Map<MethodReference, List<MethodReference>> methodReferenceMap) {
+ this.methodReferenceMap = methodReferenceMap;
+ }
+
+ public static CovariantMethodsInJarResult create() throws Exception {
+ Map<MethodReference, List<MethodReference>> methodReferenceMap = new HashMap<>();
+ CodeInspector inspector = new CodeInspector(PATH_TO_CORE_JAR);
+ DexItemFactory factory = inspector.getFactory();
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ clazz.forAllMethods(
+ method -> {
+ List<DexAnnotation> covariantAnnotations =
+ inspector.findAnnotations(
+ method.getMethod().annotations(),
+ annotation ->
+ isCovariantReturnTypeAnnotation(annotation.annotation, factory));
+ if (!covariantAnnotations.isEmpty()) {
+ MethodReference methodReference = method.asMethodReference();
+ for (DexAnnotation covariantAnnotation : covariantAnnotations) {
+ if (covariantAnnotation.annotation.type
+ == factory.annotationCovariantReturnType) {
+ createCovariantMethodReference(
+ methodReference, covariantAnnotation, methodReferenceMap);
+ } else {
+ fail("There are no such annotations present in libcore");
+ }
+ }
+ }
+ });
+ }
+ return new CovariantMethodsInJarResult(methodReferenceMap);
+ }
+
+ private static void createCovariantMethodReference(
+ MethodReference methodReference,
+ DexAnnotation covariantAnnotation,
+ Map<MethodReference, List<MethodReference>> methodReferenceMap) {
+ DexValueType newReturnType =
+ covariantAnnotation.annotation.getElement(0).getValue().asDexValueType();
+ methodReferenceMap
+ .computeIfAbsent(methodReference, ignoreKey(ArrayList::new))
+ .add(
+ Reference.method(
+ methodReference.getHolderClass(),
+ methodReference.getMethodName(),
+ methodReference.getFormalTypes(),
+ newReturnType.value.asClassReference()));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index f11b7a1..ed2343a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -49,6 +49,17 @@
public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
ThrowableConsumer<T> setMockApiLevelForMethod(
+ MethodReference method, AndroidApiLevel apiLevel) {
+ return compilerBuilder -> {
+ compilerBuilder.addOptionsModification(
+ options -> {
+ options.apiModelingOptions().methodApiMapping.put(method, apiLevel);
+ });
+ };
+ }
+
+ public static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
+ ThrowableConsumer<T> setMockApiLevelForMethod(
Constructor<?> constructor, AndroidApiLevel apiLevel) {
return compilerBuilder -> {
compilerBuilder.addOptionsModification(
diff --git a/src/test/java/com/android/tools/r8/apimodel/JavaSourceCodePrinter.java b/src/test/java/com/android/tools/r8/apimodel/JavaSourceCodePrinter.java
new file mode 100644
index 0000000..fe1c78a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/JavaSourceCodePrinter.java
@@ -0,0 +1,264 @@
+// Copyright (c) 2022, 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.apimodel;
+
+import com.android.tools.r8.utils.Action;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public final class JavaSourceCodePrinter {
+
+ private final Set<String> imports = new HashSet<>();
+ private final String className;
+ private final String packageName;
+ private final String classModifiers;
+ private final String header;
+ private final StringBuilder bodyPrinter = new StringBuilder();
+
+ public enum KnownType {
+ Consumer("java.util.function", "Consumer", false),
+ DexItemFactory("com.android.tools.r8.graph", "DexItemFactory", false),
+ DexMethod("com.android.tools.r8.graph", "DexMethod", false);
+
+ private String packageName;
+ private String simpleName;
+ private boolean isPrimitive;
+
+ KnownType(String packageName, String simpleName, boolean isPrimitive) {
+ this.packageName = packageName;
+ this.simpleName = simpleName;
+ this.isPrimitive = isPrimitive;
+ }
+
+ boolean isPrimitive() {
+ return isPrimitive;
+ }
+
+ String getCanonicalName() {
+ return packageName + "." + simpleName;
+ }
+
+ String getSimpleName() {
+ return simpleName;
+ }
+ }
+
+ public JavaSourceCodePrinter(
+ String className, String packageName, String classModifiers, String header) {
+ this.className = className;
+ this.packageName = packageName;
+ this.classModifiers = classModifiers;
+ this.header = header;
+ }
+
+ public void addClassImport(KnownType type) {
+ if (!type.isPrimitive) {
+ imports.add(type.getCanonicalName());
+ }
+ }
+
+ public JavaSourceCodePrinter addMethod(
+ String modifiers,
+ Type returnType,
+ String name,
+ List<MethodParameter> parameters,
+ Consumer<JavaSourceCodeMethodPrinter> content) {
+ bodyPrinter.append(modifiers).append(" ");
+ if (returnType != null) {
+ bodyPrinter.append(returnType.toString(this::addClassImport));
+ } else {
+ bodyPrinter.append("void");
+ }
+ JavaSourceCodeMethodPrinter javaSourceCodeMethodPrinter = new JavaSourceCodeMethodPrinter();
+ content.accept(javaSourceCodeMethodPrinter);
+ bodyPrinter
+ .append(" ")
+ .append(name)
+ .append(
+ StringUtils.join(
+ ", ",
+ parameters,
+ parameter -> parameter.toString(this::addClassImport),
+ BraceType.PARENS))
+ .append(" {")
+ .append(StringUtils.LINE_SEPARATOR)
+ .append(javaSourceCodeMethodPrinter)
+ .append("}")
+ .append(StringUtils.LINE_SEPARATOR);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(header);
+ if (packageName != null) {
+ sb.append("package ").append(packageName).append(";").append(StringUtils.LINE_SEPARATOR);
+ }
+ sb.append(
+ StringUtils.joinLines(
+ imports.stream()
+ .sorted()
+ .map(imp -> "import " + imp + ";")
+ .collect(Collectors.toList())))
+ .append(StringUtils.LINE_SEPARATOR);
+ return sb.append(StringUtils.LINE_SEPARATOR)
+ .append(classModifiers)
+ .append(" ")
+ .append("class ")
+ .append(className)
+ .append(" {")
+ .append(StringUtils.LINE_SEPARATOR)
+ .append(bodyPrinter)
+ .append("}")
+ .toString();
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+ private String className;
+ private String packageName = null;
+ private final String classModifiers = "public";
+ private String header = "";
+
+ public Builder setClassName(String className) {
+ this.className = className;
+ return this;
+ }
+
+ public Builder setPackageName(String packageName) {
+ this.packageName = packageName;
+ return this;
+ }
+
+ public Builder setHeader(String header) {
+ this.header = header;
+ return this;
+ }
+
+ public JavaSourceCodePrinter build() {
+ assert className != null;
+ return new JavaSourceCodePrinter(className, packageName, classModifiers, header);
+ }
+ }
+
+ public static class JavaSourceCodeMethodPrinter {
+
+ private final StringBuilder sb = new StringBuilder();
+
+ public JavaSourceCodeMethodPrinter addInstanceMethodCall(
+ String variable, String method, Action... content) {
+ return addInstanceMethodCall(variable, method, Arrays.asList(content));
+ }
+
+ public JavaSourceCodeMethodPrinter addInstanceMethodCall(
+ String variable, String method, Collection<Action> content) {
+ sb.append(variable).append(".").append(method).append("(");
+ boolean insertSeparator = false;
+ for (Action action : content) {
+ if (insertSeparator) {
+ sb.append(", ");
+ }
+ action.execute();
+ insertSeparator = true;
+ }
+ sb.append(")");
+ return this;
+ }
+
+ public Action literal(String constant) {
+ return () -> sb.append(StringUtils.quote(constant));
+ }
+
+ public JavaSourceCodeMethodPrinter addSemicolon() {
+ sb.append(";");
+ return this;
+ }
+
+ public JavaSourceCodeMethodPrinter newLine() {
+ sb.append(StringUtils.LINE_SEPARATOR);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+ }
+
+ public static class Type {
+
+ private final KnownType type;
+
+ private Type(KnownType type) {
+ this.type = type;
+ }
+
+ public static Type fromType(KnownType type) {
+ return new Type(type);
+ }
+
+ public String toString(Consumer<KnownType> classConsumer) {
+ classConsumer.accept(type);
+ return type.getSimpleName();
+ }
+ }
+
+ public static class ParameterizedType extends Type {
+
+ private final Type[] arguments;
+
+ private ParameterizedType(KnownType clazz, Type[] arguments) {
+ super(clazz);
+ this.arguments = arguments;
+ }
+
+ public static ParameterizedType fromType(KnownType type, Type... arguments) {
+ return new ParameterizedType(type, arguments);
+ }
+
+ @Override
+ public String toString(Consumer<KnownType> classConsumer) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(super.toString(classConsumer));
+ if (arguments.length == 0) {
+ return sb.toString();
+ }
+ sb.append("<");
+ sb.append(
+ StringUtils.join(", ", Arrays.asList(arguments), type -> type.toString(classConsumer)));
+ sb.append(">");
+ return sb.toString();
+ }
+ }
+
+ public static class MethodParameter {
+
+ private final Type type;
+ private final String name;
+
+ private MethodParameter(Type type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ public static MethodParameter build(Type type, String name) {
+ return new MethodParameter(type, name);
+ }
+
+ private String toString(Consumer<KnownType> classConsumer) {
+ return type.toString(classConsumer) + " " + name;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
index a8f662f..5c30550 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollection.java
@@ -6,6 +6,7 @@
import static java.util.Collections.emptyList;
import com.android.tools.r8.benchmarks.appdumps.TiviBenchmarks;
+import com.android.tools.r8.benchmarks.desugaredlib.L8Benchmark;
import com.android.tools.r8.benchmarks.desugaredlib.LegacyDesugaredLibraryBenchmark;
import com.android.tools.r8.benchmarks.helloworld.HelloWorldBenchmark;
import com.android.tools.r8.benchmarks.retrace.RetraceStackTraceBenchmark;
@@ -47,6 +48,7 @@
// Every benchmark that should be active on golem must be setup in this method.
HelloWorldBenchmark.configs().forEach(collection::addBenchmark);
LegacyDesugaredLibraryBenchmark.configs().forEach(collection::addBenchmark);
+ L8Benchmark.configs().forEach(collection::addBenchmark);
TiviBenchmarks.configs().forEach(collection::addBenchmark);
RetraceStackTraceBenchmark.configs().forEach(collection::addBenchmark);
return collection;
diff --git a/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
new file mode 100644
index 0000000..147d601
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/desugaredlib/L8Benchmark.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2022, 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.benchmarks.desugaredlib;
+
+import com.android.tools.r8.L8TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestState;
+import com.android.tools.r8.benchmarks.BenchmarkBase;
+import com.android.tools.r8.benchmarks.BenchmarkConfig;
+import com.android.tools.r8.benchmarks.BenchmarkDependency;
+import com.android.tools.r8.benchmarks.BenchmarkEnvironment;
+import com.android.tools.r8.benchmarks.BenchmarkTarget;
+import com.android.tools.r8.desugar.desugaredlibrary.jdk11.DesugaredLibraryJDK11Undesugarer;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class L8Benchmark extends BenchmarkBase {
+
+ private static final BenchmarkDependency androidJar = BenchmarkDependency.getAndroidJar30();
+ private static final BenchmarkDependency jdk11Conf =
+ new BenchmarkDependency(
+ "legacyConf", "desugar_jdk_libs_11", Paths.get("third_party", "openjdk"));
+
+ public L8Benchmark(BenchmarkConfig config, TestParameters parameters) {
+ super(config, parameters);
+ }
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return parametersFromConfigs(configs());
+ }
+
+ public static List<BenchmarkConfig> configs() {
+ return ImmutableList.of(
+ BenchmarkConfig.builder()
+ .setName("L8Benchmark")
+ .setTarget(BenchmarkTarget.D8)
+ .setFromRevision(12733)
+ .setMethod(L8Benchmark::run)
+ .addDependency(androidJar)
+ .addDependency(jdk11Conf)
+ .measureRunTime()
+ .build());
+ }
+
+ public static void run(BenchmarkEnvironment environment) throws Exception {
+ Path undesugarJdkLib =
+ DesugaredLibraryJDK11Undesugarer.undesugaredJarJDK11(
+ environment.getTemp().newFolder("undesugar_jdk_lib").toPath(),
+ jdk11Conf.getRoot(environment).resolve("desugar_jdk_libs.jar"));
+ LibraryDesugaringSpecification spec =
+ new LibraryDesugaringSpecification(
+ "JDK11_Benchmark",
+ ImmutableSet.of(undesugarJdkLib),
+ Paths.get("src/library_desugar/jdk11/desugar_jdk_libs.json"),
+ ImmutableSet.of(androidJar.getRoot(environment).resolve("android.jar")),
+ "");
+ runner(environment.getConfig())
+ .setWarmupIterations(1)
+ .setBenchmarkIterations(10)
+ .reportResultSum()
+ .run(
+ results -> {
+ long start = System.nanoTime();
+ L8TestBuilder.create(
+ AndroidApiLevel.B, Backend.DEX, new TestState(environment.getTemp()))
+ .apply(spec::configureL8TestBuilder)
+ .compile();
+ long end = System.nanoTime();
+ results.addRuntimeResult(end - start);
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/PackageInfoClassFileWithoutAbstractAccessModifierTest.java b/src/test/java/com/android/tools/r8/cf/PackageInfoClassFileWithoutAbstractAccessModifierTest.java
new file mode 100644
index 0000000..0db0bbf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/PackageInfoClassFileWithoutAbstractAccessModifierTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2022, 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.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.Matchers;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PackageInfoClassFileWithoutAbstractAccessModifierTest extends TestBase
+ implements Opcodes {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public Backend backend;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withNoneRuntime().build(), Backend.values());
+ }
+
+ private static final String CLASS_NAME = "it.unimi.dsi.fastutil.package-info";
+
+ private void inspect(CodeInspector inspector, boolean isR8) {
+ ClassSubject packageInfo = inspector.clazz(CLASS_NAME);
+ assertThat(packageInfo, Matchers.isInterface());
+ assertEquals(backend.isDex() || isR8, packageInfo.isAbstract());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(backend)
+ .addProgramClassFileData(dump())
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .inspect(inspector -> inspect(inspector, false));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(backend)
+ .addProgramClassFileData(dump())
+ .addKeepMainRule(CLASS_NAME)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .inspect(inspector -> inspect(inspector, true));
+ }
+
+ // Some versions of javac would generate package-info class files without ACC_ABSTRACT.
+ // Dump of it/unimi/dsi/fastutil/package-info.class from fastutil-8.5.8.jar.
+ public static byte[] dump() throws Exception {
+ ClassWriter classWriter = new ClassWriter(0);
+ classWriter.visit(
+ V1_5, ACC_INTERFACE, "it/unimi/dsi/fastutil/package-info", null, "java/lang/Object", null);
+ classWriter.visitSource("package-info.java", null);
+ classWriter.visitEnd();
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
index 2c58d15..692773c 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/MethodGenerationBase.java
@@ -52,16 +52,20 @@
public String getHeaderString() {
String simpleName = getClass().getSimpleName();
+ return getHeaderString(getYear(), simpleName)
+ + StringUtils.lines("package " + getGeneratedClassPackageName() + ";");
+ }
+
+ public static String getHeaderString(int year, String simpeNameOfGenerator) {
return StringUtils.lines(
- "// Copyright (c) " + getYear() + ", the R8 project authors. Please see the AUTHORS file",
+ "// Copyright (c) " + year + ", 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.",
"",
"// ***********************************************************************************",
- "// GENERATED FILE. DO NOT EDIT! See " + simpleName + ".java.",
+ "// GENERATED FILE. DO NOT EDIT! See " + simpeNameOfGenerator + ".java.",
"// ***********************************************************************************",
- "",
- "package " + getGeneratedClassPackageName() + ";");
+ "");
}
protected Path getGeneratedFile() {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
index f8e88c6..6891000 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/NoLineInfoTest.java
@@ -10,17 +10,12 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.naming.retrace.StackTrace;
import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
-import com.android.tools.r8.references.MethodReference;
-import com.android.tools.r8.transformers.MethodTransformer;
import com.android.tools.r8.utils.BooleanUtils;
import java.io.IOException;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import org.objectweb.asm.Label;
@RunWith(Parameterized.class)
public class NoLineInfoTest extends TestBase {
@@ -47,21 +42,7 @@
private byte[] getTestClassTransformed() throws IOException {
return transformer(TestClass.class)
.setSourceFile(INPUT_SOURCE_FILE)
- .addMethodTransformer(
- new MethodTransformer() {
- private final Map<MethodReference, Integer> lines = new HashMap<>();
-
- @Override
- public void visitLineNumber(int line, Label start) {
- Integer nextLine = lines.getOrDefault(getContext().getReference(), 0);
- if (nextLine > 0) {
- super.visitLineNumber(nextLine, start);
- }
- // Increment the actual line content by 100 so that each one is clearly distinct
- // from a PC value for any of the methods.
- lines.put(getContext().getReference(), nextLine + 100);
- }
- })
+ .setPredictiveLineNumbering()
.transform();
}
@@ -145,10 +126,27 @@
// Residual line depends on the source file parameter.
private StackTraceLine residualLine(String method, int line) {
- String defaultFile =
- isCompileWithPcAsLineNumberSupport() ? UNKNOWN_SOURCE_FILE : DEFAULT_SOURCE_FILE;
- String file = customSourceFile ? CUSTOM_SOURCE_FILE : defaultFile;
- return line(file, method, line);
+ if (customSourceFile) {
+ return line(CUSTOM_SOURCE_FILE, method, line);
+ }
+ if (isCompileWithPcAsLineNumberSupport()) {
+ return line(UNKNOWN_SOURCE_FILE, method, line);
+ }
+ return line(DEFAULT_SOURCE_FILE, method, line);
+ }
+
+ // A residual line that is either null debug info or pc2pc mapping.
+ private StackTraceLine residualPcOrNoLine(String method, int pc) {
+ // If compiling with custom source file the debug info must be non-null and have a pc mapping.
+ if (customSourceFile) {
+ return line(CUSTOM_SOURCE_FILE, method, pc);
+ }
+ // If debug info is null, then on native pc support VMs it will print "unknown" and pc.
+ if (isRuntimeWithPcAsLineNumberSupport()) {
+ return line(UNKNOWN_SOURCE_FILE, method, pc);
+ }
+ // On old runtimes it will print "default" and no line info.
+ return line(DEFAULT_SOURCE_FILE, method, -1);
}
// This is the real "reference" stack trace as given by JVM on inputs and should be retraced to.
@@ -174,10 +172,18 @@
// TODO(b/232212653): The retraced stack trace should be the same as `getExpectedInputStacktrace`.
private StackTrace getUnexpectedRetracedStacktrace() {
-
- // TODO(b/232212653): Retracing the PC 1 preserves it but it should map to <noline>
- StackTraceLine fooLine =
- isRuntimeWithPcAsLineNumberSupport() ? inputLine("foo", 1) : inputLine("foo", -1);
+ StackTraceLine fooLine;
+ if (parameters.isCfRuntime()) {
+ fooLine = inputLine("foo", -1);
+ } else if (customSourceFile) {
+ // TODO(b/232212653): Should retrace convert out of "0" and represent it as <noline>/-1?
+ fooLine = inputLine("foo", 0);
+ } else if (isRuntimeWithPcAsLineNumberSupport()) {
+ // TODO(b/232212653): Retrace should convert PC 1 to line <noline>/-1/0.
+ fooLine = inputLine("foo", 1);
+ } else {
+ fooLine = inputLine("foo", -1);
+ }
// TODO(b/232212653): Normal line-opt will cause a single-line mapping. Retrace should not
// optimize that to mean it represents a single possible line. (<noline> should not match 1:x).
@@ -207,17 +213,9 @@
.build();
}
- // TODO(b/232212653): The correct line should be with CUSTOM_SOURCE_FILE and PC 1.
- // When compiling with debug info encoding PCs this is almost the expected output. The issue
- // being that even "foo" should have PC based encoding too to ensure the SF remains on
- // newer VMs too.
- StackTraceLine fooLine =
- isRuntimeWithPcAsLineNumberSupport()
- ? line(UNKNOWN_SOURCE_FILE, "foo", 1)
- : residualLine("foo", -1);
-
return StackTrace.builder()
- .add(fooLine)
+ // Foo has only <noline> on input and so it is allowed to compile it to a null debug-info.
+ .add(residualPcOrNoLine("foo", 1))
.add(residualLine("bar", 0))
.add(residualLine("baz", 0))
.add(residualLine("main", 6))
diff --git a/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java b/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java
index d786a12..596707f 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java
@@ -7,8 +7,9 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -21,8 +22,8 @@
@Parameter() public TestParameters parameters;
@Parameters(name = "{0}")
- public static List<Object[]> data() {
- return buildParameters(getTestParameters().withDexRuntimes().withAllApiLevels().build());
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build();
}
@Test
@@ -32,17 +33,15 @@
.setMinApi(parameters.getApiLevel())
.compile()
.inspect(
- // TODO(b/233857593): There used to be only one goto.
- inspector -> {
- assertEquals(
- 2,
- inspector
- .clazz(TestClass.class)
- .uniqueMethodWithName("testLoopPhiWithNullFirstInput")
- .streamInstructions()
- .filter(InstructionSubject::isGoto)
- .count());
- });
+ inspector ->
+ assertEquals(
+ 1,
+ inspector
+ .clazz(TestClass.class)
+ .uniqueMethodWithName("testLoopPhiWithNullFirstInput")
+ .streamInstructions()
+ .filter(InstructionSubject::isGoto)
+ .count()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
index 1834ea4..d31aa91 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryInvalidTest.java
@@ -5,13 +5,13 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
-import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertFalse;
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestBuilder;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
@@ -43,7 +43,7 @@
LibraryDesugaringSpecification jdk11InvalidLib =
new LibraryDesugaringSpecification(
"JDK11_INVALID_LIB",
- UNDESUGARED_JDK_11_LIB_JAR,
+ ToolHelper.getUndesugaredJdk11LibJarForTesting(),
"jdk11/desugar_jdk_libs.json",
AndroidApiLevel.L);
return buildParameters(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
index 91fa3d5..1f4d9c1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryMismatchTest.java
@@ -20,9 +20,7 @@
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.errors.DesugaredLibraryMismatchDiagnostic;
-import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.Box;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.List;
@@ -147,7 +145,8 @@
.addProgramClasses(Library.class)
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(
- LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
+ LibraryDesugaringTestConfiguration.forSpecification(
+ libraryDesugaringSpecification.getSpecification()))
.compile()
.writeToZip();
@@ -159,7 +158,8 @@
.setMinApi(parameters.getApiLevel())
.compileWithExpectedDiagnostics(
diagnostics -> {
- if (requiresAnyCoreLibDesugaring(parameters.getApiLevel())) {
+ if (requiresAnyCoreLibDesugaring(
+ parameters.getApiLevel(), libraryDesugaringSpecification != JDK8)) {
diagnostics.assertOnlyErrors();
diagnostics.assertErrorsMatch(
diagnosticType(DesugaredLibraryMismatchDiagnostic.class));
@@ -213,14 +213,12 @@
public void testMergeDifferentLibraryDesugarVersions() throws Exception {
// DEX code with library desugaring using a desugared library configuration with a
// different identifier.
- Box<LegacyDesugaredLibrarySpecification> box = new Box<>();
Path libraryDex =
testForD8(Backend.DEX)
.addProgramClasses(Library.class)
.setMinApi(parameters.getApiLevel())
.enableCoreLibraryDesugaring(
LibraryDesugaringTestConfiguration.builder()
- .setMinApi(parameters.getApiLevel())
// Minimal configuration with a different identifier.
// The j$.time is rewritten because empty flags are equivalent to an empty
// specification, and no marker is set for empty specifications.
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index b833d04..4c05352 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestState;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestBuilder;
@@ -23,28 +22,9 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import org.junit.BeforeClass;
public class DesugaredLibraryTestBase extends TestBase {
- private static final boolean FORCE_JDK11_DESUGARED_LIB = false;
-
- @BeforeClass
- public static void setUpDesugaredLibrary() {
- if (!FORCE_JDK11_DESUGARED_LIB) {
- return;
- }
- System.setProperty("desugar_jdk_json_dir", "src/library_desugar/jdk11");
- System.setProperty(
- "desugar_jdk_libs", "third_party/openjdk/desugar_jdk_libs_11/desugar_jdk_libs.jar");
- System.out.println("Forcing the usage of JDK11 desugared library.");
- }
-
- public static boolean isJDK11DesugaredLibrary() {
- String property = System.getProperty("desugar_jdk_json_dir", "");
- return property.contains("jdk11");
- }
-
// For conversions tests, we need DexRuntimes where classes to convert are present (DexRuntimes
// above N and O depending if Stream or Time APIs are used), but we need to compile the program
// with a minAPI below to force the use of conversions.
@@ -65,14 +45,6 @@
throw new Error("Unsupported conversion parameters");
}
- protected AndroidApiLevel getRequiredCompilationAPILevel() {
- return isJDK11DesugaredLibrary() ? AndroidApiLevel.R : AndroidApiLevel.P;
- }
-
- protected Path getLibraryFile() {
- return ToolHelper.getAndroidJar(getRequiredCompilationAPILevel());
- }
-
protected boolean requiresEmulatedInterfaceCoreLibDesugaring(TestParameters parameters) {
return parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
}
@@ -82,23 +54,11 @@
< (isJDK11 ? AndroidApiLevel.S.getLevel() : AndroidApiLevel.O.getLevel());
}
- protected boolean requiresTimeDesugaring(TestParameters parameters) {
- return requiresTimeDesugaring(parameters, isJDK11DesugaredLibrary());
- }
-
- protected boolean requiresAnyCoreLibDesugaring(TestParameters parameters) {
- return requiresAnyCoreLibDesugaring(parameters.getApiLevel());
- }
-
protected boolean requiresAnyCoreLibDesugaring(AndroidApiLevel apiLevel, boolean isJDK11) {
return apiLevel.getLevel()
<= (isJDK11 ? AndroidApiLevel.R.getLevel() : AndroidApiLevel.N_MR1.getLevel());
}
- protected boolean requiresAnyCoreLibDesugaring(AndroidApiLevel apiLevel) {
- return requiresAnyCoreLibDesugaring(apiLevel, isJDK11DesugaredLibrary());
- }
-
protected DesugaredLibraryTestBuilder<?> testForDesugaredLibrary(
TestParameters parameters,
LibraryDesugaringSpecification libraryDesugaringSpecification,
@@ -126,6 +86,26 @@
}
}
+ public Path getNonShrunkDesugaredLib(
+ TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification)
+ throws Exception {
+ return testForL8(parameters.getApiLevel(), parameters.getBackend())
+ .apply(libraryDesugaringSpecification::configureL8TestBuilder)
+ .compile()
+ .writeToZip();
+ }
+
+ public Path getNonShrunkDesugaredLib(
+ AndroidApiLevel apiLevel,
+ Backend backend,
+ LibraryDesugaringSpecification libraryDesugaringSpecification)
+ throws Exception {
+ return testForL8(apiLevel, backend)
+ .apply(libraryDesugaringSpecification::configureL8TestBuilder)
+ .compile()
+ .writeToZip();
+ }
+
public static Path[] getAllFilesWithSuffixInDirectory(Path directory, String suffix)
throws IOException {
return Files.walk(directory)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java
index 18d1613..dd036e2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/J$ExtensionTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
import static junit.framework.TestCase.assertTrue;
@@ -151,7 +152,7 @@
@Test
public void testJ$ExtensionDesugaring() throws Throwable {
Assume.assumeFalse(parameters.isCfRuntime());
- Assume.assumeTrue(requiresTimeDesugaring(parameters));
+ Assume.assumeTrue(requiresTimeDesugaring(parameters, libraryDesugaringSpecification != JDK8));
String stdErr =
testForDesugaredLibrary(
parameters, libraryDesugaringSpecification, compilationSpecification)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java
index 944036e..8d06a2f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LegacyDesugaredLibraryConfigurationParsingTest.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
import static com.android.tools.r8.DiagnosticsMatcher.diagnosticOrigin;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.RELEASED_1_1_5;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
@@ -15,7 +16,6 @@
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestDiagnosticMessagesImpl;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
@@ -37,7 +37,6 @@
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
-import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -45,13 +44,18 @@
@RunWith(Parameterized.class)
public class LegacyDesugaredLibraryConfigurationParsingTest extends DesugaredLibraryTestBase {
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withNoneRuntime().build();
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+ @Parameterized.Parameters(name = "{0}, spec: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withNoneRuntime().build(), ImmutableList.of(RELEASED_1_1_5));
}
- public LegacyDesugaredLibraryConfigurationParsingTest(TestParameters parameters) {
+ public LegacyDesugaredLibraryConfigurationParsingTest(
+ TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
parameters.assertNoneRuntime();
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
}
final AndroidApiLevel minApi = AndroidApiLevel.B;
@@ -87,7 +91,6 @@
}
private LegacyDesugaredLibrarySpecificationParser parser(DiagnosticsHandler handler) {
- Assume.assumeFalse(isJDK11DesugaredLibrary());
return new LegacyDesugaredLibrarySpecificationParser(
factory, new Reporter(handler), libraryCompilation, minApi.getLevel());
}
@@ -117,9 +120,7 @@
public void testReference() throws Exception {
// Just test that the reference file parses without issues.
LegacyDesugaredLibrarySpecification spec =
- runPassing(
- StringResource.fromFile(
- LibraryDesugaringSpecification.RELEASED_1_1_5.getSpecification()));
+ runPassing(StringResource.fromFile(libraryDesugaringSpecification.getSpecification()));
assertEquals(libraryCompilation, spec.isLibraryCompilation());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
index ddf8c9d..3c4a223 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LintFilesTest.java
@@ -128,7 +128,7 @@
Path jdkLibJar =
libraryDesugaringSpecification == JDK8
? ToolHelper.DESUGARED_JDK_8_LIB_JAR
- : ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
+ : ToolHelper.getUndesugaredJdk11LibJarForTesting();
GenerateLintFiles.main(
new String[] {
libraryDesugaringSpecification.getSpecification().toString(),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java
index 23a146e..3d1a75b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/LocalDateEpochTest.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexField;
@@ -18,6 +19,7 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanTopLevelFlags;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
@@ -52,7 +54,7 @@
@Test
public void testD8() throws Exception {
testForD8(parameters.getBackend())
- .addLibraryFiles(getLibraryFile())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.R))
.addProgramClasses(DesugarLocalDate.class)
.addProgramClassFileData(getMainClassFileData())
.setMinApi(parameters.getApiLevel())
@@ -66,7 +68,7 @@
public void testR8() throws Exception {
Assume.assumeTrue(parameters.isDexRuntime());
testForR8(parameters.getBackend())
- .addLibraryFiles(getLibraryFile())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.R))
.addProgramClasses(DesugarLocalDate.class)
.addProgramClassFileData(getMainClassFileData())
.addKeepMainRule(Main.class)
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
index cfb5b67..ca676d3 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/MergingWithDesugaredLibraryTest.java
@@ -11,6 +11,8 @@
import static com.android.tools.r8.MarkerMatcher.markerIsDesugared;
import static com.android.tools.r8.MarkerMatcher.markerMinApi;
import static com.android.tools.r8.MarkerMatcher.markerTool;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
@@ -25,7 +27,7 @@
import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -37,6 +39,7 @@
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 MergingWithDesugaredLibraryTest extends DesugaredLibraryTestBase {
@@ -45,14 +48,18 @@
private static final String J$_RESULT = "j$.util.stream.ReferencePipeline$Head";
private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+ @Parameters(name = "{0}, spec: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(), getJdk8Jdk11());
}
- public MergingWithDesugaredLibraryTest(TestParameters parameters) {
+ public MergingWithDesugaredLibraryTest(
+ TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
}
@Test
@@ -61,15 +68,18 @@
try {
compileResult =
testForD8()
- .addLibraryFiles(getLibraryFile())
+ .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
.addProgramFiles(buildPart1DesugaredLibrary(), buildPart2NoDesugaredLibrary())
.setMinApi(parameters.getApiLevel())
.applyIf(
someLibraryDesugaringRequired(),
b ->
b.enableCoreLibraryDesugaring(
- LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
- .compileWithExpectedDiagnostics(this::assertError);
+ LibraryDesugaringTestConfiguration.forSpecification(
+ libraryDesugaringSpecification.getSpecification())))
+ .compileWithExpectedDiagnostics(this::assertError)
+ .addRunClasspathFiles(
+ getNonShrunkDesugaredLib(parameters, libraryDesugaringSpecification));
assertFalse(expectError());
} catch (CompilationFailedException e) {
assertTrue(expectError());
@@ -108,13 +118,14 @@
Path app =
testForD8()
.addProgramFiles(buildPart1DesugaredLibrary(), shrunkenLib)
- .addLibraryFiles(getLibraryFile())
+ .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
.setMinApi(parameters.getApiLevel())
.applyIf(
someLibraryDesugaringRequired(),
b ->
b.enableCoreLibraryDesugaring(
- LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
+ LibraryDesugaringTestConfiguration.forSpecification(
+ libraryDesugaringSpecification.getSpecification())))
.compile()
.writeToZip();
@@ -123,7 +134,9 @@
allOf(
markerTool(Tool.D8),
markerIsDesugared(),
- markerHasDesugaredLibraryIdentifier(requiresAnyCoreLibDesugaring(parameters)));
+ markerHasDesugaredLibraryIdentifier(
+ requiresAnyCoreLibDesugaring(
+ parameters.getApiLevel(), libraryDesugaringSpecification != JDK8)));
assertMarkersMatch(
ExtractMarker.extractMarkerFromDexFile(app), ImmutableList.of(libraryMatcher, d8Matcher));
}
@@ -209,7 +222,8 @@
}
private boolean someLibraryDesugaringRequired() {
- return requiresAnyCoreLibDesugaring(parameters);
+ return requiresAnyCoreLibDesugaring(
+ parameters.getApiLevel(), libraryDesugaringSpecification != JDK8);
}
@Test
@@ -218,14 +232,17 @@
testForD8()
.addProgramFiles(buildPart1DesugaredLibrary())
.addProgramClasses(Part2.class)
- .addLibraryFiles(getLibraryFile())
+ .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
.setMinApi(parameters.getApiLevel())
.applyIf(
someLibraryDesugaringRequired(),
b ->
b.enableCoreLibraryDesugaring(
- LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
+ LibraryDesugaringTestConfiguration.forSpecification(
+ libraryDesugaringSpecification.getSpecification())))
.compile()
+ .addRunClasspathFiles(
+ getNonShrunkDesugaredLib(parameters, libraryDesugaringSpecification))
.inspectDiagnosticMessages(this::assertWarningPresent);
if (parameters.getApiLevel().getLevel() < AndroidApiLevel.N.getLevel()) {
compileResult
@@ -256,14 +273,15 @@
private Path buildPart1DesugaredLibrary() throws Exception {
return testForD8()
- .addLibraryFiles(getLibraryFile())
+ .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
.addProgramClasses(Part1.class)
.setMinApi(parameters.getApiLevel())
.applyIf(
someLibraryDesugaringRequired(),
b ->
b.enableCoreLibraryDesugaring(
- LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel())))
+ LibraryDesugaringTestConfiguration.forSpecification(
+ libraryDesugaringSpecification.getSpecification())))
.compile()
.writeToZip();
}
@@ -278,6 +296,7 @@
@SuppressWarnings("RedundantOperationOnEmptyContainer")
static class Part1 {
+
public static void main(String[] args) {
System.out.println(new ArrayList<>().stream().getClass().getName());
}
@@ -285,6 +304,7 @@
@SuppressWarnings("RedundantOperationOnEmptyContainer")
static class Part2 {
+
public static void main(String[] args) {
System.out.println(new ArrayList<>().stream().getClass().getName());
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
index 82ca473..9e8583f 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/NeverMergeCoreLibDesugarClasses.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.smali.SmaliBuilder;
@@ -85,9 +84,7 @@
try {
Path input =
testForL8(parameters.getApiLevel())
- .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
- .setDebug()
- .setDesugarJDKLibsCustomConversions(ToolHelper.DESUGAR_LIB_CONVERSIONS)
+ .apply(libraryDesugaringSpecification::configureL8TestBuilder)
.compile()
.writeToZip();
testForD8()
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index f503773..f5717d8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
-import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
@@ -17,6 +16,7 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -76,7 +76,7 @@
LibraryDesugaringSpecification jdk11MaxCompileSdk =
new LibraryDesugaringSpecification(
"JDK11_MAX",
- UNDESUGARED_JDK_11_LIB_JAR,
+ ToolHelper.getUndesugaredJdk11LibJarForTesting(),
"jdk11/desugar_jdk_libs.json",
AndroidApiLevel.LATEST);
return buildParameters(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
index 8ed7907..492964e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ProgramRewritingTest.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.desugar.desugaredlibrary;
import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
-import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
@@ -62,7 +61,7 @@
new LibraryDesugaringSpecification(
"JDK11_CL",
ImmutableSet.of(
- UNDESUGARED_JDK_11_LIB_JAR,
+ ToolHelper.getUndesugaredJdk11LibJarForTesting(),
ToolHelper.DESUGAR_LIB_CONVERSIONS,
ToolHelper.getCoreLambdaStubs()),
JDK11.getSpecification(),
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
index 0901073..7930dbe 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/PseudoPlatformApiTest.java
@@ -3,10 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar.desugaredlibrary;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
+
import com.android.tools.r8.LibraryDesugaringTestConfiguration;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ZipUtils.ZipBuilder;
import java.lang.reflect.InvocationTargetException;
@@ -27,17 +29,22 @@
// For context see b/229793269.
@RunWith(Parameterized.class)
-public class PseudoPlatformApiTest extends TestBase {
+public class PseudoPlatformApiTest extends DesugaredLibraryTestBase {
- @Parameter() public TestParameters parameters;
+ @Parameter(0)
+ public TestParameters parameters;
- @Parameters(name = "{0}")
+ @Parameter(1)
+ public LibraryDesugaringSpecification libraryDesugaringSpecification;
+
+ @Parameters(name = "{0}, spec: {1}")
public static List<Object[]> data() {
return buildParameters(
getTestParameters()
.withDexRuntimes()
.withApiLevelsStartingAtIncluding(AndroidApiLevel.P)
- .build());
+ .build(),
+ getJdk8Jdk11());
}
private Path androidJarAdditions() throws Exception {
@@ -104,8 +111,12 @@
.addProgramClasses(ProgramClass.class)
.setMinApi(AndroidApiLevel.H_MR2)
.enableCoreLibraryDesugaring(
- LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+ LibraryDesugaringTestConfiguration.forSpecification(
+ libraryDesugaringSpecification.getSpecification()))
.addRunClasspathFiles(androidJarAdditionsDex())
+ .addRunClasspathFiles(
+ getNonShrunkDesugaredLib(
+ AndroidApiLevel.H_MR2, parameters.getBackend(), libraryDesugaringSpecification))
.run(parameters.getRuntime(), ProgramClass.class)
.assertSuccessWithOutputLines("DEFAULT-X", "Y-DEFAULT");
}
@@ -119,9 +130,13 @@
.addProgramClasses(ProgramClass.class)
.setMinApi(AndroidApiLevel.H_MR2)
.enableCoreLibraryDesugaring(
- LibraryDesugaringTestConfiguration.forApiLevel(AndroidApiLevel.B))
+ LibraryDesugaringTestConfiguration.forSpecification(
+ libraryDesugaringSpecification.getSpecification()))
.addRunClasspathFiles(androidJarAdditionsDex())
.addRunClasspathFiles(oemDex())
+ .addRunClasspathFiles(
+ getNonShrunkDesugaredLib(
+ AndroidApiLevel.H_MR2, parameters.getBackend(), libraryDesugaringSpecification))
.run(parameters.getRuntime(), ProgramClass.class)
.assertSuccessWithOutputLines("OEM-X", "Y-OEM");
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
index 6d0d72f..91721e2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/RetargetAndBackportTest.java
@@ -8,8 +8,8 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyRewritingFlags;
import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyTopLevelFlags;
@@ -31,15 +31,19 @@
public class RetargetAndBackportTest extends DesugaredLibraryTestBase implements Opcodes {
private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
- @Parameters(name = "{0} {1}")
+ @Parameters(name = "{0}")
public static List<Object[]> data() {
return buildParameters(
- getTestParameters().withDexRuntime(Version.DEFAULT).withCfRuntime(CfVm.JDK11).build());
+ getTestParameters().withDexRuntime(Version.DEFAULT).withCfRuntime(CfVm.JDK11).build(),
+ LibraryDesugaringSpecification.getJdk8Jdk11());
}
- public RetargetAndBackportTest(TestParameters parameters) {
+ public RetargetAndBackportTest(
+ TestParameters parameters, LibraryDesugaringSpecification libraryDesugaringSpecification) {
this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
}
/**
@@ -65,9 +69,9 @@
@Test
public void test() throws Exception {
testForL8(AndroidApiLevel.B, parameters.getBackend())
- .noDefaultDesugarJDKLibs()
.addProgramClassFileData(dump())
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()))
+ .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+ .setDesugaredLibrarySpecification(libraryDesugaringSpecification.getSpecification())
.addOptionsModifier(RetargetAndBackportTest::specifyDesugaredLibrary)
.compile()
.inspect(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
index ed4b818..985e6d4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionAndMergeTest.java
@@ -67,7 +67,8 @@
.setMinApi(parameters.getApiLevel())
.addProgramClasses(cls)
.enableCoreLibraryDesugaring(
- LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
+ LibraryDesugaringTestConfiguration.forSpecification(
+ libraryDesugaringSpecification.getSpecification()))
.compile()
.writeToZip();
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java
index 24169b1..69b83bf 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/ConversionsPresentTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.desugar.desugaredlibrary.conversiontests;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.getJdk8Jdk11;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
@@ -64,7 +65,7 @@
assertTrue(inspector.clazz("j$.util.LongSummaryStatisticsConversions").isPresent());
assertTrue(inspector.clazz("j$.util.IntSummaryStatisticsConversions").isPresent());
assertTrue(inspector.clazz("j$.util.DoubleSummaryStatisticsConversions").isPresent());
- } else if (requiresTimeDesugaring(parameters)) {
+ } else if (requiresTimeDesugaring(parameters, libraryDesugaringSpecification != JDK8)) {
assertEquals(1, conversionsClasses.size());
assertTrue(inspector.clazz("j$.time.TimeConversions").isPresent());
} else {
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
index b5175a1..e2ecec4 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/DesugaredLibraryJDK11Undesugarer.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
-import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.transformers.MethodTransformer;
@@ -16,7 +15,6 @@
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.zip.ZipEntry;
@@ -39,22 +37,10 @@
.put("wrapper/adapter/HybridFileTypeDetector", "java/adapter/HybridFileTypeDetector")
.build();
- public static void main(String[] args) throws Exception {
- setUpDesugaredLibrary();
- undesugaredJar();
- }
-
- public static Path undesugaredJar() {
- if (!isJDK11DesugaredLibrary()) {
- return ToolHelper.getDesugarJDKLibsBazelGeneratedFile();
- }
- return undesugaredJarJDK11(ToolHelper.getDesugarJDKLibsBazelGeneratedFile());
- }
-
- public static Path undesugaredJarJDK11(Path jdk11Jar) {
- String string = jdk11Jar.toString();
- Path desugaredLibJDK11Undesugared =
- Paths.get(string.substring(0, string.length() - 4) + "_undesugared.jar");
+ public static Path undesugaredJarJDK11(Path undesugarFolder, Path jdk11Jar) {
+ String fileName = jdk11Jar.getFileName().toString();
+ String newFileName = fileName.substring(0, fileName.length() - 4) + "_undesugared.jar";
+ Path desugaredLibJDK11Undesugared = undesugarFolder.resolve(newFileName);
return generateUndesugaredJar(jdk11Jar, desugaredLibJDK11Undesugared);
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
index f586605..7d09ee0 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11AtomicTests.java
@@ -5,8 +5,8 @@
package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getPathsFiles;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getPathsFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
index 0c4e38a..a3641d6 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11ConcurrentMapTests.java
@@ -5,8 +5,8 @@
package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getPathsFiles;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getPathsFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
new file mode 100644
index 0000000..5cb8e57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
@@ -0,0 +1,325 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
+
+import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.EXTENSION_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.JDK11_PATH_JAVA_BASE_EXT;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestCompileResult;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+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 Jdk11NioFileTests extends DesugaredLibraryTestBase {
+
+ private static final Path JDK_11_NIO_TEST_FILES_DIR =
+ Paths.get(ToolHelper.JDK_11_TESTS_DIR).resolve("java/nio/file");
+ private static Path TEST_UTIL_JAR;
+ private static List<byte[]> TEST_PROGRAM_CLASS_DATA;
+
+ private final TestParameters parameters;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
+ private final CompilationSpecification compilationSpecification;
+
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() throws Exception {
+ List<LibraryDesugaringSpecification> specs;
+ if (ToolHelper.isWindows()) {
+ // The library configuration is not available on windows. Do not run anything.
+ specs = ImmutableList.of();
+ } else {
+ Jdk11TestLibraryDesugaringSpecification.setUp();
+ specs = ImmutableList.of(JDK11_PATH_JAVA_BASE_EXT);
+ }
+ return buildParameters(
+ // TODO(134732760): Support Dalvik VMs, currently fails because libjavacrypto is required
+ // and present only in ART runtimes.
+ getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V5_1_1)
+ .withAllApiLevels()
+ .build(),
+ specs,
+ ImmutableList.of(D8_L8DEBUG, D8_L8SHRINK));
+ }
+
+ public Jdk11NioFileTests(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
+ this.parameters = parameters;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
+ this.compilationSpecification = compilationSpecification;
+ }
+
+ private static final List<String> EXCLUDE_COMPILATION =
+ ImmutableList.copyOf(
+ new String[] {
+ // We cannot run these tests due to missing dependencies.
+ "java/nio/file/FileStore/Basic.java",
+ "java/nio/file/Path/MacPathTest.java",
+ "java/nio/file/WatchService/LotsOfEvents.java",
+ "java/nio/file/Files/StreamLinesTest.java",
+ "java/nio/file/Files/walkFileTree/FindTest.java",
+ "java/nio/file/Files/DeleteOnClose.java",
+ "java/nio/file/Files/CopyAndMove.java",
+ "java/nio/file/FileSystem/Basic.java",
+ // Skip module info not used on Android.
+ "module-info.java"
+ });
+
+ // We distinguish 2 kinds of tests:
+ // - Main tests, which are run by running the main method, and succeed if no error is raised.
+ // - TestNG tests, which are run using testNG.
+ private static final List<String> SUCCESSFUL_MAIN_TESTS =
+ ImmutableList.of(
+ "PathUriImportExport",
+ "PathMisc",
+ "probeContentTypeParallelProbes",
+ "probeContentTypeForceLoad",
+ "AclEntryEmptySet",
+ "DirectoryStreamBasic",
+ "DirectoryStreamSecureDS",
+ "DirectoryStreamDriveLetter",
+ "FileTimeBasic",
+ "BasicFileAttributeViewCreationTime",
+ "BasicFileAttributeViewBasic",
+ "etcExceptions",
+ "walkFileTreeTerminateWalk",
+ "walkFileTreeNulls",
+ "walkFileTreeCreateFileTree",
+ "walkFileTreeMaxDepth",
+ "walkFileTreeSkipSiblings",
+ "walkFileTreeSkipSubtree",
+ "FilesSBC",
+ "FilesNameLimits",
+ "FilesCustomOptions",
+ "FilesLinks",
+ "WatchServiceBasic",
+ "WatchServiceFileTreeModifier",
+ "WatchServiceDeleteInterference",
+ "WatchServiceMayFlies",
+ "WatchServiceLotsOfCancels",
+ "WatchServiceSensitivityModifier");
+ private static final List<String> FAILING_MAIN_TESTS =
+ ImmutableList.of(
+ "PathPathOps",
+ "PathMacPath",
+ "DosFileAttributeViewBasic",
+ "probeContentTypeBasic",
+ "AclFileAttributeViewBasic",
+ "UserDefinedFileAttributeViewBasic",
+ "PosixFileAttributeViewBasic",
+ "BasicFileAttributeViewUnixSocketFile",
+ "p/pMain",
+ "PathMatcherBasic",
+ "walkFileTreeWalkWithSecurity",
+ "FilesFileAttributes",
+ "FilesInterruptCopy",
+ "FilesTemporaryFiles",
+ "FilesCheckPermissions",
+ "FilesMisc",
+ "WatchServiceWithSecurityManager",
+ "WatchServiceUpdateInterference",
+ "WatchServiceLotsOfCloses");
+ private static final List<String> SUCCESSFUL_TESTNG_TESTS =
+ ImmutableList.of("FilesStreamTest", "FilesBytesAndLines");
+ private static final List<String> FAILING_TESTNG_TESTS =
+ ImmutableList.of("spiSetDefaultProvider", "FilesReadWriteString");
+
+ @BeforeClass
+ public static void compileJdk11NioTests() throws Exception {
+ Map<String, List<Path>> nioTestFileBuckets = getSourceFileBuckets();
+ TEST_UTIL_JAR = jarTestUtils(nioTestFileBuckets.get("file"));
+ nioTestFileBuckets.remove("file");
+ TEST_PROGRAM_CLASS_DATA = new ArrayList<>();
+ for (Entry<String, List<Path>> entry : nioTestFileBuckets.entrySet()) {
+ Path[] compiledClasses = compile(entry.getKey(), entry.getValue(), TEST_UTIL_JAR);
+ assert compiledClasses.length > 0;
+ TEST_PROGRAM_CLASS_DATA.addAll(repackage(entry.getKey(), compiledClasses));
+ }
+ assert !TEST_PROGRAM_CLASS_DATA.isEmpty();
+ }
+
+ @NotNull
+ private static Map<String, List<Path>> getSourceFileBuckets() throws IOException {
+ Map<String, List<Path>> nioTestFileBuckets =
+ Files.walk(JDK_11_NIO_TEST_FILES_DIR)
+ .filter(path -> path.toString().endsWith(JAVA_EXTENSION))
+ .filter(
+ path ->
+ EXCLUDE_COMPILATION.stream().noneMatch(elem -> path.toString().endsWith(elem)))
+ .collect(Collectors.groupingBy(item -> item.getParent().getFileName().toString()));
+ assert nioTestFileBuckets.size() > 0;
+ return nioTestFileBuckets;
+ }
+
+ private static Path jarTestUtils(List<Path> sourceFiles) throws IOException {
+ Path[] fileCompiledClasses = compile("file", sourceFiles, null);
+ String jarName = "testUtils.jar";
+ Path output = fileCompiledClasses[0].getParent();
+ List<String> cmdline = new ArrayList<>();
+ cmdline.add(TestRuntime.getCheckedInJdk11().getJavaExecutable().getParent() + "/jar");
+ cmdline.add("cf");
+ cmdline.add(jarName);
+ for (Path compile : fileCompiledClasses) {
+ cmdline.add(output.relativize(compile).toString());
+ }
+ ProcessBuilder builder = new ProcessBuilder(cmdline);
+ builder.directory(output.toFile());
+ ProcessResult result = ToolHelper.runProcess(builder);
+ assert result.exitCode == 0;
+ return output.resolve(jarName);
+ }
+
+ private static List<byte[]> repackage(String prefix, Path[] compiledClasses) throws IOException {
+ List<byte[]> data = new ArrayList<>();
+ Map<String, String> rewrite = new HashMap<>();
+ for (Path compiledClass : compiledClasses) {
+ String fileName = compiledClass.getFileName().toString();
+ String basicName = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
+ rewrite.put(basicName, prefix + basicName);
+ }
+ for (Path compiledClass : compiledClasses) {
+ String fileName = compiledClass.getFileName().toString();
+ String basicName = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
+ ClassFileTransformer classFileTransformer =
+ transformer(compiledClass, Reference.classFromDescriptor("L" + basicName + ";"))
+ .setClassDescriptor("L" + rewrite.get(basicName) + ";")
+ .setSuper(type -> rewrite.getOrDefault(type, type))
+ .rewriteEnlosingAndNestAttributes(type -> rewrite.getOrDefault(type, type));
+ rewrite.forEach(
+ (key, val) -> {
+ classFileTransformer.replaceClassDescriptorInMethodInstructions(
+ "L" + key + ";", "L" + val + ";");
+ classFileTransformer.replaceClassDescriptorInMembers("L" + key + ";", "L" + val + ";");
+ });
+ data.add(classFileTransformer.transform());
+ }
+ return data;
+ }
+
+ private static Path[] compile(String name, List<Path> sourceFiles, Path cp) throws IOException {
+ List<String> options =
+ Arrays.asList(
+ "--add-reads",
+ "java.base=ALL-UNNAMED",
+ "--patch-module",
+ "java.base=" + EXTENSION_PATH);
+ Path tmpDirectory = getStaticTemp().newFolder(name).toPath();
+ List<Path> classpath = new ArrayList<>();
+ classpath.add(EXTENSION_PATH);
+ classpath.add(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar"));
+ if (cp != null) {
+ classpath.add(cp);
+ }
+ javac(TestRuntime.getCheckedInJdk11(), getStaticTemp())
+ .addOptions(options)
+ .addClasspathFiles(classpath)
+ .addSourceFiles(sourceFiles)
+ .setOutputPath(tmpDirectory)
+ .compile();
+ return getAllFilesWithSuffixInDirectory(tmpDirectory, CLASS_EXTENSION);
+ }
+
+ @Test
+ public void testNioFileDesugaredLib() throws Exception {
+ String verbosity = "2";
+ DesugaredLibraryTestCompileResult<?> compileResult =
+ testForDesugaredLibrary(
+ parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addProgramFiles(TEST_UTIL_JAR)
+ .addProgramClassFileData(TEST_PROGRAM_CLASS_DATA)
+ .addProgramFiles(testNGSupportProgramFiles())
+ .compile()
+ .withArt6Plus64BitsLib();
+ int success = 0;
+ for (String mainTestClass : SUCCESSFUL_MAIN_TESTS) {
+ SingleTestRunResult<?> run = compileResult.run(parameters.getRuntime(), mainTestClass);
+ if (run.getExitCode() != 0) {
+ System.out.println("Main Fail " + mainTestClass);
+ } else {
+ success++;
+ }
+ }
+ for (String testNGTestClass : SUCCESSFUL_TESTNG_TESTS) {
+ SingleTestRunResult<?> result =
+ compileResult.run(
+ parameters.getRuntime(), "TestNGMainRunner", verbosity, testNGTestClass);
+ if (!result.getStdOut().contains(StringUtils.lines(testNGTestClass + ": SUCCESS"))) {
+ System.out.println("TestNG Fail " + testNGTestClass);
+ } else {
+ success++;
+ }
+ }
+ // TODO(b/234689867): Understand and fix these issues.
+ // Most issues seem to come from the missing secure.properties file. This file is not accessed
+ // in all tests on all API levels, hence a different number of failures on each level.
+ assertTrue(success >= 15);
+ }
+
+ @Test
+ public void testNioFileAndroid() throws Exception {
+ Assume.assumeFalse(
+ "The package java.nio was not present on older devices, all tests fail.",
+ parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0));
+ String verbosity = "2";
+ D8TestCompileResult compileResult =
+ testForD8(parameters.getBackend())
+ .addProgramFiles(TEST_UTIL_JAR)
+ .addProgramClassFileData(TEST_PROGRAM_CLASS_DATA)
+ .addProgramFiles(testNGSupportProgramFiles())
+ .addLibraryFiles(libraryDesugaringSpecification.getLibraryFiles())
+ .compile()
+ .withArt6Plus64BitsLib();
+ for (String mainTestClass : SUCCESSFUL_MAIN_TESTS) {
+ compileResult.run(parameters.getRuntime(), mainTestClass).assertSuccess();
+ }
+ for (String testNGTestClass : SUCCESSFUL_TESTNG_TESTS) {
+ SingleTestRunResult<?> result =
+ compileResult.run(
+ parameters.getRuntime(), "TestNGMainRunner", verbosity, testNGTestClass);
+ assertTrue(
+ "Failure in " + testNGTestClass + "\n" + result,
+ result.getStdOut().contains(StringUtils.lines(testNGTestClass + ": SUCCESS")));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java
index b6b3cb1..94b9162 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11StreamAbstractTests.java
@@ -5,9 +5,9 @@
package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getPathsFiles;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getSafeVarArgsFile;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getPathsFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getSafeVarArgsFile;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.EXTENSION_PATH;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.JDK11_PATH_JAVA_BASE_EXT;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.JDK8_JAVA_BASE_EXT;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11Paths.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11SupportFiles.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11Paths.java
rename to src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11SupportFiles.java
index 1409b1b..d3507a2 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11Paths.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11SupportFiles.java
@@ -14,7 +14,7 @@
// Provides convenience to use Paths/SafeVarargs which are missing on old Android but
// required by some Jdk tests, and for java.base extensions.
-public class Jdk11Paths {
+public class Jdk11SupportFiles {
private static final Path ANDROID_PATHS_FILES_DIR =
Paths.get("third_party/android_jar/lib-v26/xxx/");
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java
index c208900..6249cf1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TestLibraryDesugaringSpecification.java
@@ -32,10 +32,10 @@
private static final String EXTENSION_STRING = "build/libs/java_base_extension.jar";
- private static Path[] JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES;
- private static Path JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR;
private static final Path JDK_11_JAVA_BASE_EXTENSION_FILES_DIR =
Paths.get("third_party/openjdk/jdk-11-test/lib/testlibrary/bootlib/java.base");
+ private static final Path JDK_11_TESTLIBRARY_FILES_DIR =
+ Paths.get("third_party/openjdk/jdk-11-test/lib/testlibrary/jdk");
public static Path EXTENSION_PATH;
@@ -43,14 +43,22 @@
public static LibraryDesugaringSpecification JDK11_JAVA_BASE_EXT;
public static LibraryDesugaringSpecification JDK11_PATH_JAVA_BASE_EXT;
- private static Path[] getJavaBaseExtensionsFiles() throws Exception {
+ private static Path[] getExtensionsFiles() throws Exception {
Path[] files =
getAllFilesWithSuffixInDirectory(JDK_11_JAVA_BASE_EXTENSION_FILES_DIR, JAVA_EXTENSION);
assert files.length > 0;
- return files;
+ Path[] files2 = getAllFilesWithSuffixInDirectory(JDK_11_TESTLIBRARY_FILES_DIR, JAVA_EXTENSION);
+ assert files2.length > 0;
+ List<Path> paths = new ArrayList<>(Arrays.asList(files));
+ Collections.addAll(paths, files2);
+ return paths.toArray(new Path[0]);
}
public static void setUp() throws Exception {
+ if (ToolHelper.isWindows()) {
+ // The library configuration is not available on windows. Do not run anything.
+ return;
+ }
EXTENSION_PATH = Paths.get(EXTENSION_STRING);
ensureJavaBaseExtensionsCompiled();
JDK8_JAVA_BASE_EXT = createSpecification("JDK8_JAVA_BASE_EXT", JDK8);
@@ -76,7 +84,7 @@
TemporaryFolder folder =
new TemporaryFolder(ToolHelper.isLinux() ? null : Paths.get("build", "tmp").toFile());
folder.create();
- JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR = folder.newFolder("jdk11JavaBaseExt").toPath();
+ Path output = folder.newFolder("jdk11Ext").toPath();
List<String> options =
Arrays.asList(
"--add-reads",
@@ -87,30 +95,29 @@
.addOptions(options)
.addClasspathFiles(
Collections.singletonList(Paths.get(JDK_TESTS_BUILD_DIR + "testng-6.10.jar")))
- .addSourceFiles(getJavaBaseExtensionsFiles())
- .setOutputPath(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR)
+ .addSourceFiles(getExtensionsFiles())
+ .setOutputPath(output)
.compile();
- JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES =
- getAllFilesWithSuffixInDirectory(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR, CLASS_EXTENSION);
- assert JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES.length > 0;
+ Path[] toCompile = getAllFilesWithSuffixInDirectory(output, CLASS_EXTENSION);
+ assert toCompile.length > 0;
// Jar the contents.
List<String> cmdline = new ArrayList<>();
cmdline.add(TestRuntime.getCheckedInJdk11().getJavaExecutable().getParent() + "/jar");
cmdline.add("cf");
cmdline.add("tmp.jar");
- for (Path compile : JDK_11_JAVA_BASE_EXTENSION_COMPILED_FILES) {
- cmdline.add(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.relativize(compile).toString());
+ for (Path compile : toCompile) {
+ cmdline.add(output.relativize(compile).toString());
}
ProcessBuilder builder = new ProcessBuilder(cmdline);
- builder.directory(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.toFile());
+ builder.directory(output.toFile());
ProcessResult result = ToolHelper.runProcess(builder);
assert result.exitCode == 0;
// Move the result into the build/libs folder.
List<String> cmdlineMv = new ArrayList<>();
cmdlineMv.add("mv");
- cmdlineMv.add(JDK_11_JAVA_BASE_EXTENSION_CLASSES_DIR.resolve("tmp.jar").toString());
+ cmdlineMv.add(output.resolve("tmp.jar").toString());
cmdlineMv.add(EXTENSION_STRING);
ProcessResult resultMv = ToolHelper.runProcess(new ProcessBuilder(cmdlineMv));
assert resultMv.exitCode == 0;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
index 6cf5f77..ee4cad8 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11TimeAbstractTests.java
@@ -5,8 +5,8 @@
package com.android.tools.r8.desugar.desugaredlibrary.jdktests;
import static com.android.tools.r8.ToolHelper.JDK_TESTS_BUILD_DIR;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.getPathsFiles;
-import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11Paths.testNGSupportProgramFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.getPathsFiles;
+import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11SupportFiles.testNGSupportProgramFiles;
import static com.android.tools.r8.desugar.desugaredlibrary.jdktests.Jdk11TestLibraryDesugaringSpecification.EXTENSION_PATH;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
index c7cda9c..9567481 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/r8ondex/HelloWorldCompiledOnArtTest.java
@@ -4,30 +4,32 @@
package com.android.tools.r8.desugar.desugaredlibrary.r8ondex;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8DEBUG;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK8;
import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.D8;
-import com.android.tools.r8.D8TestBuilder;
-import com.android.tools.r8.D8TestCompileResult;
-import com.android.tools.r8.D8TestRunResult;
-import com.android.tools.r8.LibraryDesugaringTestConfiguration;
import com.android.tools.r8.R8;
-import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification;
+import com.android.tools.r8.desugar.desugaredlibrary.test.DesugaredLibraryTestCompileResult;
+import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.List;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -57,17 +59,27 @@
}
private final TestParameters parameters;
+ private final CompilationSpecification compilationSpecification;
+ private final LibraryDesugaringSpecification libraryDesugaringSpecification;
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters()
- .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
- .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
- .build();
+ @Parameters(name = "{0}, spec: {1}, {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters()
+ .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
+ .build(),
+ ImmutableList.of(JDK8, JDK11_PATH),
+ ImmutableList.of(D8_L8DEBUG));
}
- public HelloWorldCompiledOnArtTest(TestParameters parameters) {
+ public HelloWorldCompiledOnArtTest(
+ TestParameters parameters,
+ LibraryDesugaringSpecification libraryDesugaringSpecification,
+ CompilationSpecification compilationSpecification) {
this.parameters = parameters;
+ this.compilationSpecification = compilationSpecification;
+ this.libraryDesugaringSpecification = libraryDesugaringSpecification;
}
private static String commandLinePathFor(String string) {
@@ -101,18 +113,17 @@
@Test
public void testHelloCompiledWithD8Dex() throws Exception {
Path helloOutput = temp.newFolder("helloOutput").toPath().resolve("out.zip").toAbsolutePath();
- D8TestRunResult run =
- compileR8ToDexWithD8()
- .run(
- parameters.getRuntime(),
- D8.class,
- "--release",
- "--output",
- helloOutput.toString(),
- "--lib",
- commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
- HELLO_PATH);
- run.assertSuccess();
+ compileR8ToDexWithD8()
+ .run(
+ parameters.getRuntime(),
+ D8.class,
+ "--release",
+ "--output",
+ helloOutput.toString(),
+ "--lib",
+ commandLinePathFor(ToolHelper.JAVA_8_RUNTIME),
+ HELLO_PATH)
+ .assertSuccess();
verifyResult(helloOutput);
}
@@ -121,32 +132,29 @@
assertEquals(StringUtils.lines("Hello, world"), processResult.stdout);
}
- private D8TestCompileResult compileR8ToDexWithD8() throws Exception {
- D8TestBuilder d8TestBuilder =
- testForD8().addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR);
- if (parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel()) {
- d8TestBuilder.addProgramFiles(getPathBackport());
- }
- D8TestCompileResult compile =
- d8TestBuilder
- .addLibraryFiles(getLibraryFile())
- .setMinApi(parameters.getApiLevel())
- .enableCoreLibraryDesugaring(
- LibraryDesugaringTestConfiguration.forApiLevel(parameters.getApiLevel()))
- .addOptionsModification(
- options -> {
- options.testing.enableD8ResourcesPassThrough = true;
- options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
- options.testing.trackDesugaredAPIConversions = true;
- })
- .compile();
- TestDiagnosticMessages diagnosticMessages = compile.getDiagnosticMessages();
- assertTrue(
- diagnosticMessages.getWarnings().isEmpty()
- || diagnosticMessages.getWarnings().stream()
- .noneMatch(x -> x.getDiagnosticMessage().contains("andThen")));
- return compile
- .withArt6Plus64BitsLib()
- .withArtFrameworks();
+ private DesugaredLibraryTestCompileResult<?> compileR8ToDexWithD8() throws Exception {
+ Path[] pathBackport = getPathBackport();
+ return testForDesugaredLibrary(
+ parameters, libraryDesugaringSpecification, compilationSpecification)
+ .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
+ .applyIf(
+ parameters.getApiLevel().getLevel() < AndroidApiLevel.O.getLevel()
+ && libraryDesugaringSpecification != JDK11_PATH,
+ b -> b.addProgramFiles(pathBackport))
+ .addOptionsModification(
+ options -> {
+ options.testing.enableD8ResourcesPassThrough = true;
+ options.dataResourceConsumer = options.programConsumer.getDataResourceConsumer();
+ options.testing.trackDesugaredAPIConversions = true;
+ })
+ .compile()
+ .inspectDiagnosticMessages(
+ diagnosticMessages -> {
+ assertTrue(
+ diagnosticMessages.getWarnings().isEmpty()
+ || diagnosticMessages.getWarnings().stream()
+ .noneMatch(x -> x.getDiagnosticMessage().contains("andThen")));
+ })
+ .withArt6Plus64BitsLib();
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
index e72e4e7..b9ca3f1 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/DesugaredLibraryTestBuilder.java
@@ -70,10 +70,8 @@
.setMode(compilationSpecification.getProgramCompilationMode());
LibraryDesugaringTestConfiguration.Builder libraryConfBuilder =
LibraryDesugaringTestConfiguration.builder()
- .setMinApi(parameters.getApiLevel())
.addDesugaredLibraryConfiguration(
- StringResource.fromFile(libraryDesugaringSpecification.getSpecification()))
- .dontAddRunClasspath();
+ StringResource.fromFile(libraryDesugaringSpecification.getSpecification()));
if (compilationSpecification.isL8Shrink() && !compilationSpecification.isCfToCf()) {
keepRuleConsumer = new TestingKeepRuleConsumer();
libraryConfBuilder.setKeepRuleConsumer(keepRuleConsumer);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
index 65171c1..edff04e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/test/LibraryDesugaringSpecification.java
@@ -3,9 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.desugar.desugaredlibrary.test;
+import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_11_LIB_JAR;
import static com.android.tools.r8.ToolHelper.DESUGARED_JDK_8_LIB_JAR;
import static com.android.tools.r8.ToolHelper.DESUGARED_LIB_RELEASES_DIR;
-import static com.android.tools.r8.ToolHelper.UNDESUGARED_JDK_11_LIB_JAR;
+import static com.android.tools.r8.ToolHelper.getUndesugaredJdk11LibJarForTesting;
import com.android.tools.r8.L8TestBuilder;
import com.android.tools.r8.ToolHelper;
@@ -25,17 +26,20 @@
"JDK8", DESUGARED_JDK_8_LIB_JAR, "desugar_jdk_libs.json", AndroidApiLevel.P);
public static LibraryDesugaringSpecification JDK11 =
new LibraryDesugaringSpecification(
- "JDK11", UNDESUGARED_JDK_11_LIB_JAR, "jdk11/desugar_jdk_libs.json", AndroidApiLevel.R);
+ "JDK11",
+ getUndesugaredJdk11LibJarForTesting(),
+ "jdk11/desugar_jdk_libs.json",
+ AndroidApiLevel.R);
public static LibraryDesugaringSpecification JDK11_MINIMAL =
new LibraryDesugaringSpecification(
"JDK11_MINIMAL",
- UNDESUGARED_JDK_11_LIB_JAR,
+ getUndesugaredJdk11LibJarForTesting(),
"jdk11/desugar_jdk_libs_minimal.json",
AndroidApiLevel.R);
public static LibraryDesugaringSpecification JDK11_PATH =
new LibraryDesugaringSpecification(
"JDK11_PATH",
- UNDESUGARED_JDK_11_LIB_JAR,
+ getUndesugaredJdk11LibJarForTesting(),
"jdk11/desugar_jdk_libs_path.json",
AndroidApiLevel.R);
@@ -43,20 +47,20 @@
public static LibraryDesugaringSpecification JDK11_PATH_ALTERNATIVE_3 =
new LibraryDesugaringSpecification(
"JDK11_PATH_ALTERNATIVE_3",
- UNDESUGARED_JDK_11_LIB_JAR,
+ getUndesugaredJdk11LibJarForTesting(),
"jdk11/desugar_jdk_libs_path_alternative_3.json",
AndroidApiLevel.R);
public static LibraryDesugaringSpecification JDK11_CHM_ONLY =
new LibraryDesugaringSpecification(
"JDK11_CHM_ONLY",
- UNDESUGARED_JDK_11_LIB_JAR,
+ getUndesugaredJdk11LibJarForTesting(),
"jdk11/chm_only_desugar_jdk_libs.json",
AndroidApiLevel.R);
public static LibraryDesugaringSpecification JDK11_LEGACY =
new LibraryDesugaringSpecification(
"JDK11_LEGACY",
// The legacy specification is not using the undesugared JAR.
- Paths.get("third_party/openjdk/desugar_jdk_libs_11/desugar_jdk_libs.jar"),
+ DESUGARED_JDK_11_LIB_JAR,
"jdk11/desugar_jdk_libs_legacy.json",
AndroidApiLevel.R);
public static final LibraryDesugaringSpecification RELEASED_1_0_9 =
@@ -141,8 +145,7 @@
l8TestBuilder
.addProgramFiles(getDesugarJdkLibs())
.addLibraryFiles(getLibraryFiles())
- .setDesugaredLibraryConfiguration(getSpecification())
- .noDefaultDesugarJDKLibs()
+ .setDesugaredLibrarySpecification(getSpecification())
.applyIf(
l8Shrink,
builder -> {
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaMethodsWithModifiedAccessTest.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaMethodsWithModifiedAccessTest.java
new file mode 100644
index 0000000..61b75de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaMethodsWithModifiedAccessTest.java
@@ -0,0 +1,146 @@
+// Copyright (c) 2022, 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.lambdas;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isNative;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPrivate;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.function.Function;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+// See b/234475018) for context.
+@RunWith(Parameterized.class)
+public class LambdaMethodsWithModifiedAccessTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private static final String LAMBDA_TO_PUBLIC = "lambda$withPublicLambdaMethod$0";
+ private static final String LAMBDA_TO_NATIVE = "lambda$withNativeLambdaMethod$1";
+ private static final String EXPECTED_OUTPUT =
+ StringUtils.lines("withPublicLambdaMethod", "UnsatisfiedLinkError: withNativeLambdaMethod");
+
+ @BeforeClass
+ public static void checkJavacLambdas() throws IOException {
+ CodeInspector inspector =
+ new CodeInspector(ToolHelper.getClassFileForTestClass(LambdaTest.class));
+ inspector.forAllClasses(clazz -> clazz.forAllMethods(System.out::println));
+ assertThat(
+ inspector.clazz(LambdaTest.class).uniqueMethodWithName(LAMBDA_TO_PUBLIC), isPrivate());
+ assertThat(
+ inspector.clazz(LambdaTest.class).uniqueMethodWithName(LAMBDA_TO_NATIVE), isPrivate());
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(
+ inspector.clazz(LambdaTest.class).uniqueMethodWithName(LAMBDA_TO_PUBLIC), isPublic());
+ assertThat(
+ inspector.clazz(LambdaTest.class).uniqueMethodWithName(LAMBDA_TO_NATIVE), isNative());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(getTransformedLambdaTest())
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::inspect)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ // TODO(b/234475018): Compilation hits an AssertionError which checks expected javac generated
+ // lambda methods in LambdaDescriptor.lookupTargetMethod.
+ // assert target == null
+ // || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
+ // || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
+ // || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
+ @Test(expected = CompilationFailedException.class)
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(getTransformedLambdaTest())
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ }
+
+ // TODO(b/234475018): Compilation hits an AssertionError which checks expected javac generated
+ // lambda methods in LambdaDescriptor.lookupTargetMethod.
+ // assert target == null
+ // || (implHandle.type.isInvokeInstance() && isInstanceMethod(target))
+ // || (implHandle.type.isInvokeDirect() && isPrivateInstanceMethod(target))
+ // || (implHandle.type.isInvokeDirect() && isPublicizedInstanceMethod(target));
+ @Test(expected = CompilationFailedException.class)
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addProgramClassFileData(getTransformedLambdaTest())
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile();
+ }
+
+ private byte[] getTransformedLambdaTest() throws Exception {
+ return transformer(LambdaTest.class)
+ .setAccessFlags(MethodPredicate.onName(LAMBDA_TO_NATIVE), MethodAccessFlags::setNative)
+ .setAccessFlags(MethodPredicate.onName(LAMBDA_TO_PUBLIC), AccessFlags::promoteToPublic)
+ .removeMethodsCodeAndAnnotations(MethodPredicate.onName(LAMBDA_TO_NATIVE))
+ .transform();
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new LambdaTest().withPublicLambdaMethod().apply(null);
+ try {
+ new LambdaTest().withNativeLambdaMethod().apply(null);
+ } catch (UnsatisfiedLinkError e) {
+ if (e.getMessage().contains(LAMBDA_TO_NATIVE)) {
+ System.out.println("UnsatisfiedLinkError: withNativeLambdaMethod");
+ } else {
+ System.out.println("UnsatisfiedLinkError: with unexpected content");
+ }
+ }
+ }
+ }
+
+ public static class LambdaTest {
+ String f;
+
+ Function<Void, String> withPublicLambdaMethod() {
+ return (ignored) -> {
+ System.out.println("withPublicLambdaMethod");
+ return f;
+ };
+ }
+
+ Function<Void, String> withNativeLambdaMethod() {
+ return (ignored) -> f;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
index 3e44232..2554cf0 100644
--- a/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/records/EmptyRecordTest.java
@@ -4,15 +4,20 @@
package com.android.tools.r8.desugar.records;
-import com.android.tools.r8.R8FullTestBuilder;
+import static org.junit.Assume.assumeFalse;
+
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class EmptyRecordTest extends TestBase {
@@ -21,17 +26,20 @@
private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
private static final String EXPECTED_RESULT_D8 = StringUtils.lines("Empty[]");
- private static final String EXPECTED_RESULT_R8 = StringUtils.lines("a[]");
+ private static final String EXPECTED_RESULT_R8_MINIFICATION = StringUtils.lines("a[]");
+ private static final String EXPECTED_RESULT_R8_NO_MINIFICATION =
+ StringUtils.lines("EmptyRecord$Empty[]");
- private final TestParameters parameters;
+ @Parameter(0)
+ public boolean enableMinification;
- public EmptyRecordTest(TestParameters parameters) {
- this.parameters = parameters;
- }
+ @Parameter(1)
+ public TestParameters parameters;
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{1}, minification: {0}")
public static List<Object[]> data() {
return buildParameters(
+ BooleanUtils.values(),
getTestParameters()
.withCfRuntimesStartingFromIncluding(CfVm.JDK17)
.withDexRuntimes()
@@ -41,6 +49,7 @@
@Test
public void testD8AndJvm() throws Exception {
+ assumeFalse("Only applicable for R8", enableMinification);
if (parameters.isCfRuntime()) {
testForJvm()
.addProgramClassFileData(PROGRAM_DATA)
@@ -57,20 +66,28 @@
@Test
public void testR8() throws Exception {
- R8FullTestBuilder builder =
- testForR8(parameters.getBackend())
- .addProgramClassFileData(PROGRAM_DATA)
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(MAIN_TYPE);
- if (parameters.isCfRuntime()) {
- builder
- .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
- .compile()
- .inspect(RecordTestUtils::assertRecordsAreRecords)
- .run(parameters.getRuntime(), MAIN_TYPE)
- .assertSuccessWithOutput(EXPECTED_RESULT_R8);
- return;
- }
- builder.run(parameters.getRuntime(), MAIN_TYPE).assertSuccessWithOutput(EXPECTED_RESULT_R8);
+ // TODO(b/233857841): Should always succeed.
+ AssertUtils.assertFailsCompilationIf(
+ parameters.isDexRuntime() && !enableMinification,
+ () ->
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .addKeepMainRule(MAIN_TYPE)
+ .applyIf(
+ parameters.isCfRuntime(),
+ testBuilder ->
+ testBuilder.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)))
+ .minification(enableMinification)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .applyIf(
+ parameters.isCfRuntime(),
+ compileResult ->
+ compileResult.inspect(RecordTestUtils::assertRecordsAreRecords))
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(
+ enableMinification
+ ? EXPECTED_RESULT_R8_MINIFICATION
+ : EXPECTED_RESULT_R8_NO_MINIFICATION));
}
}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
new file mode 100644
index 0000000..78fc404
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/PermittedSubclassesAttributeInDexTest.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2022, 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.sealed;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.transformers.ClassFileTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class PermittedSubclassesAttributeInDexTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("true", "true");
+
+ @Test
+ public void testRuntime() throws Exception {
+ assumeTrue(
+ parameters.isCfRuntime()
+ && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17)
+ && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+ testForJvm()
+ .addProgramClassFileData(getTransformedClasses())
+ .addProgramClasses(Sub1.class, Sub2.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertEquals(
+ ImmutableList.of(
+ inspector.clazz(Sub1.class).asTypeSubject(),
+ inspector.clazz(Sub2.class).asTypeSubject()),
+ inspector.clazz(C.class).getFinalPermittedSubclassAttributes());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .addProgramClasses(Sub1.class, Sub2.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.emitPermittedSubclassesAnnotationsInDex = true)
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ // No Art versions have support for sealed classes yet.
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+
+ public Collection<byte[]> getTransformedClasses() throws Exception {
+ ClassFileTransformer transformer =
+ transformer(TestClass.class)
+ .setMinVersion(CfVm.JDK17)
+ .transformMethodInsnInMethod(
+ "main",
+ ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+ if (owner.equals(DescriptorUtils.getClassBinaryName(AdditionalClassAPIs.class))) {
+ if (name.equals("getPermittedSubclasses")) {
+ continuation.visitMethodInsn(
+ Opcodes.INVOKEVIRTUAL,
+ "java/lang/Class",
+ "getPermittedSubclasses",
+ "()[Ljava/lang/Class;",
+ false);
+ } else if (name.equals("isSealed")) {
+ continuation.visitMethodInsn(
+ Opcodes.INVOKEVIRTUAL, "java/lang/Class", "isSealed", "()Z", false);
+ } else {
+ fail("Unsupported rewriting of API " + owner + "." + name + descriptor);
+ }
+ } else {
+ continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ }));
+
+ return ImmutableList.of(
+ transformer.transform(),
+ transformer(C.class).setPermittedSubclasses(C.class, Sub1.class, Sub2.class).transform());
+ }
+
+ static class AdditionalClassAPIs {
+ public static Class<?>[] getPermittedSubclasses(Class<?> clazz) {
+ throw new RuntimeException();
+ }
+
+ public static boolean isSealed(Class<?> clazz) {
+ throw new RuntimeException();
+ }
+ }
+
+ static class TestClass {
+
+ public static boolean sameArrayContent(Class<?>[] array1, Class<?>[] array2) {
+ Set<Class<?>> expected = new HashSet<>(Arrays.asList(array1));
+ for (Class<?> clazz : array2) {
+ if (!expected.remove(clazz)) {
+ return false;
+ }
+ }
+ return expected.isEmpty();
+ }
+
+ public static void main(String[] args) {
+ System.out.println(AdditionalClassAPIs.isSealed(C.class));
+ System.out.println(
+ sameArrayContent(
+ new Class<?>[] {Sub1.class, Sub2.class},
+ AdditionalClassAPIs.getPermittedSubclasses(C.class)));
+ }
+ }
+
+ static class C {}
+
+ static class Sub1 extends C {}
+
+ static class Sub2 extends C {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
index 3a5ba83..e57e215 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedAttributeTest.java
@@ -10,76 +10,76 @@
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DesugarTestConfiguration;
+import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.examples.jdk17.Sealed;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import java.util.List;
+import com.android.tools.r8.utils.StringUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class SealedAttributeTest extends TestBase {
- private final TestParameters parameters;
- private final Backend backend;
+ @Parameter(0)
+ public TestParameters parameters;
- @Parameters(name = "{0}, backend:{1}")
- public static List<Object[]> data() {
- return buildParameters(
- getTestParameters().withCfRuntimesStartingFromIncluding(CfVm.JDK17).build(),
- Backend.values());
- }
+ static final String EXPECTED = StringUtils.lines("R8 compiler", "D8 compiler");
- public SealedAttributeTest(TestParameters parameters, Backend backend) {
- this.parameters = parameters;
- this.backend = backend;
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
}
@Test
public void testJvm() throws Exception {
- assumeTrue(backend == Backend.CF);
+ assumeTrue(parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17));
testForJvm()
.addRunClasspathFiles(Sealed.jar())
.run(parameters.getRuntime(), Sealed.Main.typeName())
- .assertSuccessWithOutputLines("R8 compiler", "D8 compiler");
+ .assertSuccessWithOutput(EXPECTED);
}
@Test
- public void testD8() throws Exception {
- assertThrows(
- CompilationFailedException.class,
- () -> {
- testForD8(backend)
- .addProgramFiles(Sealed.jar())
- .setMinApi(AndroidApiLevel.B)
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertErrorThatMatches(
- diagnosticMessage(
- containsString("Sealed classes are not supported as program classes")));
- });
- });
+ public void testDesugaring() throws Exception {
+ testForDesugaring(parameters)
+ .addProgramFiles(Sealed.jar())
+ .run(parameters.getRuntime(), Sealed.Main.typeName())
+ .applyIf(
+ c ->
+ DesugarTestConfiguration.isNotJavac(c)
+ || parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK17),
+ r -> r.assertSuccessWithOutput(EXPECTED),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
}
@Test
public void testR8() throws Exception {
- assertThrows(
- CompilationFailedException.class,
- () -> {
- testForR8(backend)
- .addProgramFiles(Sealed.jar())
- .setMinApi(AndroidApiLevel.B)
- .addKeepMainRule(Sealed.Main.typeName())
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertErrorThatMatches(
- diagnosticMessage(
- containsString("Sealed classes are not supported as program classes")));
- });
- });
+ R8FullTestBuilder builder =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(Sealed.jar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Sealed.Main.typeName());
+ if (parameters.isCfRuntime()) {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ builder.compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertErrorThatMatches(
+ diagnosticMessage(
+ containsString(
+ "Sealed classes are not supported as program classes")))));
+ } else {
+ builder
+ .run(parameters.getRuntime(), Sealed.Main.typeName())
+ .assertSuccessWithOutput(EXPECTED);
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java
new file mode 100644
index 0000000..0cb64c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultMethodInvokeSuperOnDefaultLibraryMethodTest.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2022, 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.desugaring.interfacemethods;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DefaultMethodInvokeSuperOnDefaultLibraryMethodTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().build();
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("1", "2");
+
+ private boolean runtimeHasConsumerInterface(TestParameters parameters) {
+ // java,util.function.Consumer was introduced at API level 24.
+ return parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+ }
+
+ @Test
+ public void testD8WithDefaultInterfaceMethodDesugaringWithAPIInLibrary() throws Exception {
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addInnerClasses(getClass())
+ .setMinApi(AndroidApiLevel.I_MR1)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyWarnings()
+ .assertWarningsMatch(
+ allOf(
+ diagnosticType(StringDiagnostic.class),
+ diagnosticMessage(
+ containsString(
+ "Interface method desugaring has inserted NoSuchMethodError"
+ + " replacing a super call in")),
+ diagnosticMessage(containsString("forEachPrint")))))
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ // If the platform does not have java.util.function.Consumer the lambda instantiation
+ // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+ // Otherwise, the generated code will throw NoSuchMethodError.
+ runtimeHasConsumerInterface(parameters),
+ b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+ b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testD8WithDefaultInterfaceMethodDesugaringWithoutAPIInLibrary() throws Exception {
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.M))
+ .addInnerClasses(getClass())
+ .setMinApi(AndroidApiLevel.I_MR1)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyWarnings()
+ .assertWarningsMatch(
+ diagnosticType(InterfaceDesugarMissingTypeDiagnostic.class)))
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ // If the platform does not have java.util.function.Consumer the lambda instantiation
+ // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+ // Otherwise, the generated code will throw NoSuchMethodError.
+ runtimeHasConsumerInterface(parameters),
+ b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+ b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testD8WithDefaultInterfaceMethodSupport() throws Exception {
+ assumeTrue(
+ parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+ testForD8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addInnerClasses(getClass())
+ .setMinApi(AndroidApiLevel.N)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8WithDefaultInterfaceMethodDesugaringWithAPIInLibrary() throws Exception {
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addInnerClasses(getClass())
+ .setMinApi(AndroidApiLevel.I_MR1)
+ .addKeepMainRule(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+
+ @Test
+ public void testR8WithDefaultInterfaceMethodDesugaringWithoutAPIInLibrary() throws Exception {
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.M))
+ .addInnerClasses(getClass())
+ .setMinApi(AndroidApiLevel.I_MR1)
+ .addKeepMainRule(TestClass.class)
+ .addDontWarn(Consumer.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .applyIf(
+ // If the platform does not have java.util.function.Consumer the lambda instantiation
+ // will throw NoClassDefFoundError as it implements java.util.function.Consumer.
+ // Otherwise, the generated code will throw NoSuchMethodError.
+ runtimeHasConsumerInterface(parameters),
+ b -> b.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+ b -> b.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testR8WithDefaultInterfaceMethodSupport() throws Exception {
+ assumeTrue(
+ parameters.asDexRuntime().getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N));
+ testForR8(parameters.getBackend())
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T))
+ .addInnerClasses(getClass())
+ .setMinApi(AndroidApiLevel.N)
+ .addKeepMainRule(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ interface IntegerIterable extends Iterable<Integer> {
+ default void forEachPrint() {
+ Iterable.super.forEach(System.out::println);
+ }
+ }
+
+ static class IntegerIterable1And2 implements IntegerIterable {
+
+ @Override
+ public Iterator<Integer> iterator() {
+ List<Integer> result = new ArrayList<>();
+ result.add(1);
+ result.add(2);
+ return result.iterator();
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new IntegerIterable1And2().forEachPrint();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
index ee0e845..9b23bd1 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -7,7 +7,6 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner;
@@ -28,6 +27,7 @@
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.objectweb.asm.ClassReader;
@@ -137,10 +137,18 @@
ToolHelper.getClassAsBytes(TestMainDefault0.class));
}
- @Test(expected = CompilationFailedException.class)
+ @Test
@IgnoreForRangeOfVmVersions(from = Version.V7_0_0, to = Version.V13_0_0) // No desugaring
public void testInvokeDefault1() throws Exception {
- ensureSameOutput(
+ ensureCustomCheck(
+ (javaResult, d8Result, r8Result, r8ShakenResult) -> {
+ Assert.assertEquals(1, d8Result.exitCode);
+ Assert.assertTrue(d8Result.stderr.contains("NoSuchMethodError"));
+ Assert.assertEquals(1, r8Result.exitCode);
+ Assert.assertTrue(r8Result.stderr.contains("NoSuchMethodError"));
+ // R8 can determine that the super interface invokes are all overridden in the program
+ Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
+ },
TestMainDefault1.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(),
getArgs(AndroidApiLevel.N.getLevel()),
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/CheckEnumUnboxedTest.java b/src/test/java/com/android/tools/r8/enumunboxing/CheckEnumUnboxedTest.java
new file mode 100644
index 0000000..c5a786c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/CheckEnumUnboxedTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2022, 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 static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.CheckEnumUnboxed;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.CheckEnumUnboxedDiagnostic;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CheckEnumUnboxedTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ AssertUtils.assertFailsCompilation(
+ () ->
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableCheckEnumUnboxedAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticType(CheckEnumUnboxedDiagnostic.class),
+ diagnosticMessage(
+ equalTo(
+ "Enum unboxing checks failed."
+ + System.lineSeparator()
+ + "Enum "
+ + EscapingEnum.class.getTypeName()
+ + " was not unboxed."))));
+ }));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ EscapingEnum escapingEnum = System.currentTimeMillis() > 0 ? EscapingEnum.A : EscapingEnum.B;
+ System.out.println(escapingEnum);
+ UnboxedEnum unboxedEnum = System.currentTimeMillis() > 0 ? UnboxedEnum.A : UnboxedEnum.B;
+ System.out.println(unboxedEnum.ordinal());
+ }
+ }
+
+ @CheckEnumUnboxed
+ enum EscapingEnum {
+ A,
+ B
+ }
+
+ @CheckEnumUnboxed
+ enum UnboxedEnum {
+ A,
+ B
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
index c412e9f..56aed18 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1620Test.java
@@ -121,11 +121,9 @@
.setMinApi(getApiLevel())
.enableCoreLibraryDesugaring(
LibraryDesugaringTestConfiguration.builder()
- .setMinApi(getApiLevel())
.setKeepRuleConsumer(keepRuleConsumer)
.addDesugaredLibraryConfiguration(
StringResource.fromFile(getDesugaredLibraryConfiguration()))
- .dontAddRunClasspath()
.build())
.compile()
.assertAllInfoMessagesMatch(
@@ -139,8 +137,8 @@
private L8TestCompileResult compileDesugaredLibraryWithL8(KeepRuleConsumer keepRuleConsumer)
throws CompilationFailedException, IOException, ExecutionException {
return testForL8(getApiLevel())
- .setDesugaredLibraryConfiguration(getDesugaredLibraryConfiguration())
- .setDesugarJDKLibs(getDesugaredLibraryJDKLibs())
+ .setDesugaredLibrarySpecification(getDesugaredLibraryConfiguration())
+ .addProgramFiles(getDesugaredLibraryJDKLibs())
.addGeneratedKeepRules(keepRuleConsumer.get())
.addKeepRuleFiles(getDesugaredLibraryKeepRuleFiles())
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
index 493df28..9d13687 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -34,6 +34,7 @@
null,
null,
Collections.emptyList(),
+ Collections.emptyList(),
null,
Collections.emptyList(),
ClassSignature.noSignature(),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java
index 1231e9b..87961dc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/whyareyounotinlining/WhyAreYouNotInliningInvokeWithUnknownTargetTest.java
@@ -44,7 +44,7 @@
.addKeepMainRule(TestClass.class)
.addKeepRules("-whyareyounotinlining class " + A.class.getTypeName() + " { void m(); }")
.addOptionsModification(options -> options.testing.whyAreYouNotInliningConsumer = out)
- .enableProguardTestOptions()
+ .enableExperimentalWhyAreYouNotInlining()
.enableNoHorizontalClassMergingAnnotations()
.compile();
out.close();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java
index 2a420cf..99de3f5 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/LibraryFieldPropagationTest.java
@@ -35,7 +35,8 @@
@Parameterized.Parameters(name = "{0}, with assume values rule: {1}")
public static List<Object[]> data() {
- return buildParameters(getTestParameters().withAllRuntimes().build(), BooleanUtils.values());
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
public LibraryFieldPropagationTest(TestParameters parameters, boolean withAssumeValuesRule) {
@@ -71,7 +72,7 @@
withAssumeValuesRule
? "-assumevalues class java.lang.Thread { public int MIN_PRIORITY return 1; }"
: "")
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifyFieldValueNotPropagated)
.run(parameters.getRuntime(), MAIN)
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MinSdkMemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MinSdkMemberValuePropagationTest.java
deleted file mode 100644
index 76f50e2..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/MinSdkMemberValuePropagationTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.ir.optimize.membervaluepropagation;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.utils.codeinspector.ClassSubject;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class MinSdkMemberValuePropagationTest extends TestBase {
-
- private final TestParameters parameters;
- private final String rule;
- private final String value;
-
- @Parameterized.Parameters(name = "{0}, rule: {1}, value: {2}")
- public static List<Object[]> data() {
- return buildParameters(
- getTestParameters().withAllRuntimes().build(),
- ImmutableList.of("assumenosideeffects", "assumevalues"),
- ImmutableList.of("42", "42..43"));
- }
-
- public MinSdkMemberValuePropagationTest(TestParameters parameters, String rule, String value) {
- this.parameters = parameters;
- this.rule = rule;
- this.value = value;
- }
-
- @Test
- public void test() throws Exception {
- testForR8(parameters.getBackend())
- .addProgramClasses(TestClass.class)
- .addKeepMainRule(TestClass.class)
- .addKeepRules(
- "-" + rule + " class " + Library.class.getTypeName() + " {",
- " static int MIN_SDK return " + value + ";",
- "}")
- .addLibraryClasses(Library.class)
- .addLibraryFiles(runtimeJar(parameters))
- .setMinApi(parameters.getRuntime())
- .compile()
- .inspect(this::verifyOutput)
- .addRunClasspathFiles(
- testForR8(parameters.getBackend())
- .addProgramClasses(Library.class)
- .addKeepAllClassesRule()
- .setMinApi(parameters.getRuntime())
- .compile()
- .writeToZip())
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello world!");
- }
-
- private void verifyOutput(CodeInspector inspector) {
- ClassSubject classSubject = inspector.clazz(TestClass.class);
- assertThat(classSubject, isPresent());
-
- MethodSubject mainSubject = classSubject.mainMethod();
- assertThat(mainSubject, isPresent());
-
- boolean readsMinSdkField =
- mainSubject
- .streamInstructions()
- .anyMatch(x -> x.isStaticGet() && x.getField().name.toString().equals("MIN_SDK"));
- assertEquals(rule.equals("assumevalues"), readsMinSdkField);
- }
-
- static class TestClass {
-
- public static void main(String[] args) {
- if (Library.MIN_SDK >= 42) {
- System.out.println("Hello world!");
- }
- }
- }
-
- static class Library {
-
- static int MIN_SDK = -1;
- }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java
new file mode 100644
index 0000000..0d6c727
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/SdkIntMemberValuePropagationTest.java
@@ -0,0 +1,170 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.membervaluepropagation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.AccessFlags;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class SdkIntMemberValuePropagationTest extends TestBase {
+
+ private enum Compiler {
+ D8,
+ R8
+ }
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public String rule;
+
+ @Parameterized.Parameters(name = "{0}, rule: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ ImmutableList.of(
+ "",
+ StringUtils.lines(
+ "-assumenosideeffects class android.os.Build$VERSION {",
+ " public static int SDK_INT return 24;",
+ "}"),
+ StringUtils.lines(
+ "-assumenosideeffects class android.os.Build$VERSION {",
+ " public static int SDK_INT return 24..25;",
+ "}"),
+ StringUtils.lines(
+ "-assumevalues class android.os.Build$VERSION {",
+ " public static int SDK_INT return 24;",
+ "}"),
+ StringUtils.lines(
+ "-assumevalues class android.os.Build$VERSION {",
+ " public static int SDK_INT return 24..29;",
+ "}")));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ assumeTrue(rule.equals(""));
+ testForD8()
+ .addProgramClassFileData(getTransformedMainClass())
+ .addLibraryClassFileData(getTransformedBuildVERSIONClass())
+ .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> verifyOutput(inspector, Compiler.D8))
+ .addRunClasspathClassFileData(getTransformedBuildVERSIONClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(getExpectedOutput(Compiler.D8));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedMainClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(rule)
+ .addLibraryClassFileData(getTransformedBuildVERSIONClass())
+ .addLibraryFiles(parameters.getDefaultRuntimeLibrary())
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> verifyOutput(inspector, Compiler.R8))
+ .addRunClasspathClassFileData(getTransformedBuildVERSIONClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(getExpectedOutput(Compiler.R8));
+ }
+
+ private String getExpectedOutput(Compiler compiler) {
+ if (compiler == Compiler.D8) {
+ return parameters.getApiLevel().isLessThan(AndroidApiLevel.N) ? "<N" : ">=N";
+ } else {
+ if (rule.equals("")) {
+ if (parameters.isDexRuntime()) {
+ return getExpectedOutput(Compiler.D8);
+ }
+ return "-1";
+ }
+ return ">=N";
+ }
+ }
+
+ private static byte[] getTransformedMainClass() throws IOException {
+ return transformer(Main.class)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(VERSION.class), "Landroid/os/Build$VERSION;")
+ .transform();
+ }
+
+ private byte[] getTransformedBuildVERSIONClass() throws IOException, NoSuchFieldException {
+ return transformer(VERSION.class)
+ .setClassDescriptor("Landroid/os/Build$VERSION;")
+ .setAccessFlags(VERSION.class.getDeclaredField("SDK_INT"), AccessFlags::setFinal)
+ .transform();
+ }
+
+ private void verifyOutput(CodeInspector inspector, Compiler compiler) {
+ ClassSubject classSubject = inspector.clazz(Main.class);
+ assertThat(classSubject, isPresent());
+
+ MethodSubject mainSubject = classSubject.mainMethod();
+ assertThat(mainSubject, isPresent());
+
+ boolean hasIf = mainSubject.streamInstructions().anyMatch(InstructionSubject::isIf);
+ boolean readsMinSdkField =
+ mainSubject
+ .streamInstructions()
+ .anyMatch(x -> x.isStaticGet() && x.getField().getName().toString().equals("SDK_INT"));
+ if (compiler == Compiler.D8 || rule.equals("")) {
+ assertEquals(
+ parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+ hasIf);
+ assertEquals(
+ parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+ readsMinSdkField);
+ } else {
+ assertFalse(hasIf);
+ assertEquals(rule.startsWith("-assumevalues"), readsMinSdkField);
+ }
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ if (VERSION.SDK_INT >= 24) {
+ System.out.println(">=N");
+ } else if (VERSION.SDK_INT >= 0) {
+ System.out.println("<N");
+ } else {
+ System.out.println("-1");
+ }
+ }
+ }
+
+ public static class /*android.os.Build$*/ VERSION {
+
+ public static /*final*/ int SDK_INT = -1;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 2100549..90d7195 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -28,7 +28,6 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.synthesis.SyntheticItems.GlobalSyntheticsStrategy;
import com.android.tools.r8.utils.InternalOptions;
import java.util.LinkedList;
@@ -87,10 +86,7 @@
@Override
public boolean removeOrReplaceCurrentInstructionByInitClassIfPossible(
- AppView<AppInfoWithLiveness> appView,
- IRCode code,
- DexType type,
- Consumer<InitClass> consumer) {
+ AppView<?> appView, IRCode code, DexType type, Consumer<InitClass> consumer) {
throw new Unimplemented();
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index ed70ece..5d54843 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -834,6 +834,7 @@
null,
null,
Collections.emptyList(),
+ Collections.emptyList(),
null,
Collections.emptyList(),
ClassSignature.noSignature(),
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java
index 29a2fce..e472056 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingInterfaceTest.java
@@ -13,10 +13,13 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+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.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Collection;
import org.junit.BeforeClass;
@@ -75,12 +78,24 @@
}
private void test(Path compileTimeLibrary, Path runtimeLibrary) throws Exception {
+ Method m = LibraryI.class.getDeclaredMethod("m");
+ MethodReference methodReferenceForM = Reference.methodFromMethod(m);
testForR8(parameters.getBackend())
.addProgramClasses(Main.class)
.addKeepClassAndMembersRules(Main.class)
.addLibraryFiles(compileTimeLibrary)
.addDefaultRuntimeLibrary(parameters)
- .apply(setMockApiLevelForMethod(LibraryI.class.getDeclaredMethod("m"), AndroidApiLevel.B))
+ .apply(setMockApiLevelForMethod(m, AndroidApiLevel.B))
+ // The api database needs to have an api level for each target, so even though B do not
+ // define 'm', we still give it an API level.
+ .apply(
+ setMockApiLevelForMethod(
+ Reference.method(
+ Reference.classFromClass(LibraryB.class),
+ methodReferenceForM.getMethodName(),
+ methodReferenceForM.getFormalTypes(),
+ methodReferenceForM.getReturnType()),
+ AndroidApiLevel.B))
.apply(setMockApiLevelForMethod(LibraryC.class.getDeclaredMethod("m"), AndroidApiLevel.B))
.apply(setMockApiLevelForMethod(LibraryA.class.getDeclaredMethod("m"), AndroidApiLevel.N))
.setMinApi(parameters.getApiLevel())
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingSuperTypeWithApiMethodTest.java b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingSuperTypeWithApiMethodTest.java
new file mode 100644
index 0000000..c474de2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/LibraryMemberRebindingSuperTypeWithApiMethodTest.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2022, 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.memberrebinding;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import javax.security.auth.DestroyFailedException;
+import javax.security.auth.Destroyable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/* Regression test for b/234613774 */
+@RunWith(Parameterized.class)
+public class LibraryMemberRebindingSuperTypeWithApiMethodTest extends TestBase {
+
+ private static final String SECRET_KEY_DESCRIPTOR = "Ljavax/crypto/SecretKey;";
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDexRuntimes().build();
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(getMainCallingSecretKeyDestroy())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .setMinApi(AndroidApiLevel.B)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0),
+ result -> result.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+ result -> result.assertFailureWithErrorThatThrows(DestroyFailedException.class));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getMainCallingSecretKeyDestroy())
+ .addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
+ .setMinApi(AndroidApiLevel.B)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0),
+ result -> result.assertFailureWithErrorThatThrows(NoSuchMethodError.class),
+ result -> result.assertFailureWithErrorThatThrows(DestroyFailedException.class));
+ }
+
+ private byte[] getMainCallingSecretKeyDestroy() throws Exception {
+ return transformer(Main.class)
+ .setImplementsClassDescriptors(SECRET_KEY_DESCRIPTOR)
+ .replaceClassDescriptorInMethodInstructions(
+ descriptor(SecretKey.class), SECRET_KEY_DESCRIPTOR)
+ .removeMethods(MethodPredicate.onName("destroy"))
+ .transform();
+ }
+
+ public interface SecretKey extends Destroyable {
+
+ void destroy();
+ }
+
+ public static class Main implements /* javax.crypto */ SecretKey {
+
+ public static void main(String[] args) {
+
+ /* javax.crypto */ SecretKey main = new Main();
+ main.destroy();
+ }
+
+ @Override
+ public void destroy() {
+ throw new RuntimeException("Should have been removed");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
index a86f95a..ee69b91 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingInvokeSuperAbstractTest.java
@@ -72,12 +72,7 @@
assertThat(
getSystemService,
CodeMatchers.invokesMethodWithHolderAndName(
- typeName(
- parameters.isCfRuntime()
- || parameters.getApiLevel().isLessThan(AndroidApiLevel.N)
- ? LibrarySubSub.class
- : LibrarySub.class),
- "getSystemService"));
+ typeName(LibrarySubSub.class), "getSystemService"));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLines("LibrarySub::getSystemService");
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index f4158a1..4fdc4b7 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -71,6 +71,24 @@
return this;
}
+ public Builder addWithoutLineNumber(Class<?> clazz, String methodName, String fileName) {
+ return addWithoutLineNumber(clazz.getTypeName(), methodName, fileName);
+ }
+
+ public Builder addWithoutLineNumber(ClassReference clazz, String methodName, String fileName) {
+ return addWithoutLineNumber(clazz.getTypeName(), methodName, fileName);
+ }
+
+ public Builder addWithoutLineNumber(String className, String methodName, String fileName) {
+ stackTraceLines.add(
+ StackTraceLine.builder()
+ .setClassName(className)
+ .setMethodName(methodName)
+ .setFileName(fileName)
+ .build());
+ return this;
+ }
+
public Builder map(int i, Function<StackTraceLine, StackTraceLine> map) {
stackTraceLines.set(i, map.apply(stackTraceLines.get(i)));
return this;
diff --git a/src/test/java/com/android/tools/r8/retrace/InlineFunctionInPrunedClassTest.java b/src/test/java/com/android/tools/r8/retrace/InlineFunctionInPrunedClassTest.java
new file mode 100644
index 0000000..8daa8d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/InlineFunctionInPrunedClassTest.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2022, 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.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSame;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.Sets;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlineFunctionInPrunedClassTest extends TestBase {
+
+ private static final String NEW_SOURCE_FILE = "SourceFileA.java";
+ private static final String ORIGINAL_SOURCE_FILE = "InlineFunctionInPrunedClassTest.java";
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClassFileData(getAWithCustomSourceFile(), getMainWithStaticPosition())
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(NullPointerException.class)
+ .inspectStackTrace(this::checkExpectedStackTrace);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getAWithCustomSourceFile(), getMainWithStaticPosition())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepAttributeSourceFile()
+ .addKeepAttributeLineNumberTable()
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatThrows(NullPointerException.class)
+ .inspectStackTrace(
+ (stackTrace, inspector) -> {
+ // Ensure we have inlined the A class and it is not in the output.
+ assertEquals(
+ Sets.newHashSet(typeName(Main.class)),
+ inspector.allClasses().stream()
+ .map(FoundClassSubject::getFinalName)
+ .collect(Collectors.toSet()));
+ checkExpectedStackTrace(stackTrace);
+ });
+ }
+
+ private void checkExpectedStackTrace(StackTrace stackTrace) {
+ assertThat(
+ stackTrace,
+ isSame(
+ StackTrace.builder()
+ .add(
+ StackTraceLine.builder()
+ .setClassName(typeName(A.class))
+ .setMethodName("foo")
+ .setFileName(NEW_SOURCE_FILE)
+ .setLineNumber(1)
+ .build())
+ .add(
+ StackTraceLine.builder()
+ .setClassName(typeName(Main.class))
+ .setMethodName("main")
+ .setFileName(ORIGINAL_SOURCE_FILE)
+ .setLineNumber(1)
+ .build())
+ .build()));
+ }
+
+ private static byte[] getAWithCustomSourceFile() throws Exception {
+ return transformer(A.class)
+ .setSourceFile(NEW_SOURCE_FILE)
+ .setPredictiveLineNumbering(MethodPredicate.all(), 1)
+ .transform();
+ }
+
+ private static byte[] getMainWithStaticPosition() throws Exception {
+ return transformer(Main.class).setPredictiveLineNumbering(MethodPredicate.all(), 1).transform();
+ }
+
+ public static class A {
+
+ public static void foo() {
+ throw new NullPointerException();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
index 3f97cd1..31dd142 100644
--- a/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
+++ b/src/test/java/com/android/tools/r8/retrace/RetraceTests.java
@@ -38,8 +38,14 @@
import com.android.tools.r8.retrace.stacktraces.InlineFileNameStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineFileNameWithInnerClassesStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineInOutlineStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineNoLineAssumeNoInlineAmbiguousStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberAssumeNoInlineStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineNoLineNumberStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlinePreambleNoOriginalStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlinePreambleWithOriginalStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineSourceFileContextStackTrace;
+import com.android.tools.r8.retrace.stacktraces.InlineSourceFileStackTrace;
import com.android.tools.r8.retrace.stacktraces.InlineWithLineNumbersStackTrace;
import com.android.tools.r8.retrace.stacktraces.InvalidStackTrace;
import com.android.tools.r8.retrace.stacktraces.MapVersionWarningStackTrace;
@@ -213,6 +219,31 @@
}
@Test
+ public void testInlineNoLineNumberAssumeNoInlineStackTrace() throws Exception {
+ runRetraceTest(new InlineNoLineNumberAssumeNoInlineStackTrace());
+ }
+
+ @Test
+ public void testInlineNoLineAssumeNoInlineAmbiguousStackTrace() throws Exception {
+ runRetraceTest(new InlineNoLineAssumeNoInlineAmbiguousStackTrace());
+ }
+
+ @Test
+ public void testInlinePreambleWithOriginalStackTrace() throws Exception {
+ runRetraceTest(new InlinePreambleWithOriginalStackTrace());
+ }
+
+ @Test
+ public void testInlinePreambleNoOriginalStackTrace() throws Exception {
+ runRetraceTest(new InlinePreambleNoOriginalStackTrace());
+ }
+
+ @Test
+ public void testInlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace() throws Exception {
+ runRetraceTest(new InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace());
+ }
+
+ @Test
public void testCircularReferenceStackTrace() throws Exception {
// Proguard retrace (and therefore the default regular expression) will not retrace circular
// reference exceptions.
@@ -242,6 +273,11 @@
}
@Test
+ public void testInlineSourceFileStackTrace() throws Exception {
+ runRetraceTest(new InlineSourceFileStackTrace());
+ }
+
+ @Test
public void testColonInSourceFileNameStackTrace() throws Exception {
runRetraceTest(new ColonInFileNameStackTrace());
}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineAssumeNoInlineAmbiguousStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineAssumeNoInlineAmbiguousStackTrace.java
new file mode 100644
index 0000000..515d6b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineAssumeNoInlineAmbiguousStackTrace.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineNoLineAssumeNoInlineAmbiguousStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat a.foo(Unknown Source)");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ // TODO(b/231622686): Should not emit inline frame.
+ "\tat retrace.Main.method1(Main.java)",
+ "\tat retrace.Main.main(Main.java)",
+ "\t<OR> at retrace.Main.otherMain(Main.java)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ // TODO(b/231622686): Should not emit inline frame.
+ "\tat retrace.Main.void method1(java.lang.String)(Main.java:0)",
+ "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)",
+ "\t<OR> at retrace.Main.void otherMain(java.lang.String[])(Main.java)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "retrace.Main -> a:",
+ " void otherMain(java.lang.String[]) -> foo",
+ " 2:2:void method1(java.lang.String):0:0 -> foo",
+ " 2:2:void main(java.lang.String[]):0 -> foo");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberAssumeNoInlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberAssumeNoInlineStackTrace.java
new file mode 100644
index 0000000..ffc5da2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineNumberAssumeNoInlineStackTrace.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineNoLineNumberAssumeNoInlineStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat a.foo(Unknown Source)");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ // TODO(b/231622686): Should assume that no lines means no inline frames.
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat retrace.Main.method1(Main.java)",
+ "\tat retrace.Main.main(Main.java)",
+ "\t<OR> at retrace.Main.method2(Main.java)",
+ "\tat retrace.Main.main(Main.java)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ // TODO(b/231622686): Should assume that no lines means no inline frames.
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat retrace.Main.void method1(java.lang.String)(Main.java:0)",
+ "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)",
+ "\t<OR> at retrace.Main.void method2(int)(Main.java:0)",
+ "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "retrace.Main -> a:",
+ " 1:1:void method1(java.lang.String):0:0 -> foo",
+ " 1:1:void main(java.lang.String[]):0 -> foo",
+ " 2:2:void method2(int):0:0 -> foo",
+ " 2:2:void main(java.lang.String[]):0 -> foo");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace.java
new file mode 100644
index 0000000..3e52ebd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineNoLineWithBaseEntryNumberAssumeNoInlineStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat a.foo(Unknown Source)");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ // TODO(b/231622686): Should assume that no lines means no inline frames.
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat retrace.Main.main(Main.java)",
+ "\t<OR> at retrace.Main.method1(Main.java)",
+ "\tat retrace.Main.main(Main.java)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ // TODO(b/231622686): Should assume that no lines means no inline frames.
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat retrace.Main.void main(java.lang.String[])(Main.java)",
+ "\t<OR> at retrace.Main.void method1(java.lang.String)(Main.java:0)",
+ "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "retrace.Main -> a:",
+ " void main(java.lang.String[]) -> foo",
+ " 1:1:void method1(java.lang.String):0:0 -> foo",
+ " 1:1:void main(java.lang.String[]):0 -> foo");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleNoOriginalStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleNoOriginalStackTrace.java
new file mode 100644
index 0000000..47732b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleNoOriginalStackTrace.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlinePreambleNoOriginalStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat a.foo(Unknown Source)");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Arrays.asList(
+ // TODO(b/231622686): Should only include preamble
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat retrace.Main.main(Main.java)",
+ "\t<OR> at retrace.Main.method1(Main.java)",
+ "\tat retrace.Main.main(Main.java)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ // TODO(b/231622686): Should only include preamble
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat retrace.Main.void main(java.lang.String[])(Main.java)",
+ "\t<OR> at retrace.Main.void method1(java.lang.String)(Main.java:0)",
+ "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "retrace.Main -> a:",
+ " 0:1:void main(java.lang.String[]) -> foo",
+ " 2:2:void method1(java.lang.String):0:0 -> foo",
+ " 2:2:void main(java.lang.String[]):0 -> foo");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleWithOriginalStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleWithOriginalStackTrace.java
new file mode 100644
index 0000000..72c2076
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlinePreambleWithOriginalStackTrace.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlinePreambleWithOriginalStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat a.foo(Unknown Source)");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Arrays.asList(
+ // TODO(b/231622686): Should only include preamble
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat retrace.Main.main(Main.java)",
+ "\t<OR> at retrace.Main.method1(Main.java)",
+ "\tat retrace.Main.main(Main.java)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ // TODO(b/231622686): Should only include preamble
+ "Exception in thread \"main\" java.lang.NullPointerException",
+ "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)",
+ "\t<OR> at retrace.Main.void method1(java.lang.String)(Main.java:0)",
+ "\tat retrace.Main.void main(java.lang.String[])(Main.java:0)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.lines(
+ "retrace.Main -> a:",
+ " 0:1:void main(java.lang.String[]):0:0 -> foo",
+ " 2:2:void method1(java.lang.String):0:0 -> foo",
+ " 2:2:void main(java.lang.String[]):0 -> foo");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileStackTrace.java
new file mode 100644
index 0000000..9128e47
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileStackTrace.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2022, 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.retrace.stacktraces;
+
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
+import java.util.List;
+
+public class InlineSourceFileStackTrace implements StackTraceForTest {
+
+ @Override
+ public List<String> obfuscatedStackTrace() {
+ return Arrays.asList(" at b.c(Unknown Source:1)");
+ }
+
+ @Override
+ public String mapping() {
+ return StringUtils.joinLines(
+ "foo.Bar -> a:",
+ "# {\"id\":\"sourceFile\",\"fileName\":\"SourceFile1.kt\"}",
+ "foo.Baz -> b:",
+ "# {\"id\":\"sourceFile\",\"fileName\":\"SourceFile2.kt\"}",
+ " 1:1:void foo.Bar.method():22:22 -> c",
+ " 1:1:void main(java.lang.String[]):32 -> c");
+ }
+
+ @Override
+ public List<String> retracedStackTrace() {
+ return Arrays.asList(
+ " at foo.Bar.method(SourceFile1.kt:22)", " at foo.Baz.main(SourceFile2.kt:32)");
+ }
+
+ @Override
+ public List<String> retraceVerboseStackTrace() {
+ return Arrays.asList(
+ " at foo.Bar.void method()(SourceFile1.kt:22)",
+ " at foo.Baz.void main(java.lang.String[])(SourceFile2.kt:32)");
+ }
+
+ @Override
+ public int expectedWarnings() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepNonConstructorsTest.java b/src/test/java/com/android/tools/r8/shaking/KeepNonConstructorsTest.java
new file mode 100644
index 0000000..a1f492b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/KeepNonConstructorsTest.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeepNonConstructorsTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void keepConstructorTest() throws Exception {
+ testForR8(Backend.DEX)
+ .addInnerClasses(getClass())
+ .addKeepRules("-keep class " + A.class.getTypeName() + " { constructor *** *(...); }")
+ .setMinApi(AndroidApiLevel.LATEST)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.init(), isPresent());
+ assertEquals(1, aClassSubject.allMethods().size());
+ });
+ }
+
+ @Test
+ public void keepNonConstructorsTest() throws Exception {
+ testForR8(Backend.DEX)
+ .addInnerClasses(getClass())
+ .addKeepRules("-keep class " + A.class.getTypeName() + " { !constructor *** *(...); }")
+ .setMinApi(AndroidApiLevel.LATEST)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject aClassSubject = inspector.clazz(A.class);
+ assertThat(aClassSubject, isPresent());
+ assertThat(aClassSubject.uniqueMethodWithName("m"), isPresent());
+ assertEquals(1, aClassSubject.allMethods().size());
+ });
+ }
+
+ static class A {
+
+ A() {}
+
+ void m() {}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java
new file mode 100644
index 0000000..23febd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideCovariantTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+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.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentHashMap.KeySetView;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LibraryMethodOverrideCovariantTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public LibraryMethodOverrideCovariantTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean supportsKeySetView() {
+ return parameters.isCfRuntime()
+ || parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V10_0_0);
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class)
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryUser.class))
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ supportsKeySetView(),
+ result -> result.assertFailureWithErrorThatMatches(containsString("Hello World")),
+ result -> result.assertFailureWithErrorThatThrows(NoSuchMethodError.class));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getMainWithoutSyntheticBridgeForKeySet())
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(LibraryUser.class)
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableInliningAnnotations()
+ .compile()
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryUser.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailureWithErrorThatMatchesIf(
+ parameters.isCfRuntime(), containsString("Hello World"))
+ .assertFailureWithErrorThatThrowsIf(
+ !supportsKeySetView() && parameters.isDexRuntime(), NoSuchMethodError.class)
+ // TODO(b/234579501): We fail to keep the library method override.
+ .applyIf(
+ parameters.isDexRuntime() && supportsKeySetView(),
+ TestRunResult::assertSuccessWithEmptyOutput);
+ }
+
+ private byte[] getMainWithoutSyntheticBridgeForKeySet() throws Exception {
+ return transformer(Main.class)
+ .removeMethods(
+ (access, name, descriptor, signature, exceptions) ->
+ descriptor.equals("()Ljava/util/Set;"))
+ .transform();
+ }
+
+ public static class Main extends ConcurrentHashMap<String, String> {
+
+ @Override
+ @NeverInline
+ public KeySetView<String, String> keySet() {
+ throw new RuntimeException("Hello World");
+ }
+
+ public static void main(String[] args) {
+ LibraryUser.checkKeySet(new Main());
+ }
+ }
+
+ public static class LibraryUser {
+
+ public static void checkKeySet(ConcurrentHashMap<?, ?> map) {
+ KeySetView<?, ?> objects = map.keySet();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 6c05cb7..a841c79 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -183,7 +183,16 @@
public void resetAllowTestOptions() {
handler = new KeepingDiagnosticHandler();
reporter = new Reporter(handler);
- parser = new ProguardConfigurationParser(new DexItemFactory(), reporter, null, true);
+ parser =
+ new ProguardConfigurationParser(
+ new DexItemFactory(),
+ reporter,
+ ProguardConfigurationParserOptions.builder()
+ .setEnableExperimentalCheckEnumUnboxed(false)
+ .setEnableExperimentalConvertCheckNotNull(false)
+ .setEnableExperimentalWhyAreYouNotInlining(false)
+ .setEnableTestingOptions(true)
+ .build());
}
@Test
@@ -447,7 +456,7 @@
assertTrue(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
+ assertFalse(rule.getReturnValue().isNullability());
assertEquals(rule.getName().matches("returnsTrue"), rule.getReturnValue().getBoolean());
matches |= 1 << 0;
} else if (rule.getName().matches("returns1")) {
@@ -455,19 +464,16 @@
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertTrue(rule.getReturnValue().isSingleValue());
+ assertFalse(rule.getReturnValue().isNullability());
assertEquals(1, rule.getReturnValue().getValueRange().getMin());
assertEquals(1, rule.getReturnValue().getValueRange().getMax());
- assertEquals(1, rule.getReturnValue().getSingleValue());
matches |= 1 << 1;
} else if (rule.getName().matches("returns2To4")) {
assertTrue(rule.hasReturnValue());
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertFalse(rule.getReturnValue().isSingleValue());
+ assertFalse(rule.getReturnValue().isNullability());
assertEquals(2, rule.getReturnValue().getValueRange().getMin());
assertEquals(4, rule.getReturnValue().getValueRange().getMax());
matches |= 1 << 2;
@@ -476,8 +482,7 @@
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertFalse(rule.getReturnValue().isSingleValue());
+ assertFalse(rule.getReturnValue().isNullability());
assertEquals(234, rule.getReturnValue().getValueRange().getMin());
assertEquals(567, rule.getReturnValue().getValueRange().getMax());
matches |= 1 << 3;
@@ -486,31 +491,34 @@
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
- assertEquals("int", rule.getReturnValue().getField().type.toString());
- assertEquals("X", rule.getReturnValue().getField().name.toString());
+ assertFalse(rule.getReturnValue().isNullability());
+ assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+ assertEquals("X", rule.getReturnValue().getFieldName().toString());
matches |= 1 << 4;
} else if (rule.getName().matches("returnsNull")) {
assertTrue(rule.hasReturnValue());
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertTrue(rule.getReturnValue().isNull());
- assertTrue(rule.getReturnValue().isSingleValue());
+ assertTrue(rule.getReturnValue().isNullability());
+ assertTrue(rule.getReturnValue().getNullability().isDefinitelyNull());
matches |= 1 << 5;
} else if (rule.getName().matches("returnsNonNull")) {
- assertFalse(rule.hasReturnValue());
+ assertTrue(rule.hasReturnValue());
+ assertFalse(rule.getReturnValue().isBoolean());
+ assertFalse(rule.getReturnValue().isValueRange());
+ assertFalse(rule.getReturnValue().isField());
+ assertTrue(rule.getReturnValue().isNullability());
+ assertTrue(rule.getReturnValue().getNullability().isDefinitelyNotNull());
matches |= 1 << 6;
} else if (rule.getName().matches("returnsNonNullField")) {
assertTrue(rule.hasReturnValue());
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
- assertEquals("Object", rule.getReturnValue().getField().type.toString());
- assertEquals("X", rule.getReturnValue().getField().name.toString());
+ assertFalse(rule.getReturnValue().isNullability());
+ assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+ assertEquals("X", rule.getReturnValue().getFieldName().toString());
matches |= 1 << 7;
} else {
fail("Unexpected");
@@ -534,7 +542,7 @@
assertTrue(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
+ assertFalse(rule.getReturnValue().isNullability());
assertEquals(rule.getName().matches("isTrue"), rule.getReturnValue().getBoolean());
matches |= 1 << 0;
} else if (rule.getName().matches("is1")) {
@@ -542,19 +550,16 @@
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertTrue(rule.getReturnValue().isSingleValue());
+ assertFalse(rule.getReturnValue().isNullability());
assertEquals(1, rule.getReturnValue().getValueRange().getMin());
assertEquals(1, rule.getReturnValue().getValueRange().getMax());
- assertEquals(1, rule.getReturnValue().getSingleValue());
matches |= 1 << 1;
} else if (rule.getName().matches("is2To4")) {
assertTrue(rule.hasReturnValue());
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertFalse(rule.getReturnValue().isSingleValue());
+ assertFalse(rule.getReturnValue().isNullability());
assertEquals(2, rule.getReturnValue().getValueRange().getMin());
assertEquals(4, rule.getReturnValue().getValueRange().getMax());
matches |= 1 << 2;
@@ -563,8 +568,7 @@
assertFalse(rule.getReturnValue().isBoolean());
assertTrue(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertFalse(rule.getReturnValue().isSingleValue());
+ assertFalse(rule.getReturnValue().isNullability());
assertEquals(234, rule.getReturnValue().getValueRange().getMin());
assertEquals(567, rule.getReturnValue().getValueRange().getMax());
matches |= 1 << 3;
@@ -573,31 +577,36 @@
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
- assertEquals("int", rule.getReturnValue().getField().type.toString());
- assertEquals("X", rule.getReturnValue().getField().name.toString());
+ assertFalse(rule.getReturnValue().isNullability());
+ assertTrue(rule.getReturnValue().getNullability().isMaybeNull());
+ assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+ assertEquals("X", rule.getReturnValue().getFieldName().toString());
matches |= 1 << 4;
} else if (rule.getName().matches("isNull")) {
assertTrue(rule.hasReturnValue());
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertFalse(rule.getReturnValue().isField());
- assertTrue(rule.getReturnValue().isNull());
- assertTrue(rule.getReturnValue().isSingleValue());
+ assertTrue(rule.getReturnValue().isNullability());
+ assertTrue(rule.getReturnValue().getNullability().isDefinitelyNull());
matches |= 1 << 5;
} else if (rule.getName().matches("returnsNonNull")) {
- assertFalse(rule.hasReturnValue());
+ assertTrue(rule.hasReturnValue());
+ assertFalse(rule.getReturnValue().isBoolean());
+ assertFalse(rule.getReturnValue().isValueRange());
+ assertFalse(rule.getReturnValue().isField());
+ assertTrue(rule.getReturnValue().isNullability());
+ assertTrue(rule.getReturnValue().getNullability().isDefinitelyNotNull());
matches |= 1 << 6;
} else if (rule.getName().matches("returnsNonNullField")) {
assertTrue(rule.hasReturnValue());
assertFalse(rule.getReturnValue().isBoolean());
assertFalse(rule.getReturnValue().isValueRange());
assertTrue(rule.getReturnValue().isField());
- assertFalse(rule.getReturnValue().isNull());
- assertEquals("com.google.C", rule.getReturnValue().getField().holder.toString());
- assertEquals("Object", rule.getReturnValue().getField().type.toString());
- assertEquals("X", rule.getReturnValue().getField().name.toString());
+ assertFalse(rule.getReturnValue().isNullability());
+ assertTrue(rule.getReturnValue().getNullability().isDefinitelyNotNull());
+ assertEquals("com.google.C", rule.getReturnValue().getFieldHolder().getTypeName());
+ assertEquals("X", rule.getReturnValue().getFieldName().toString());
matches |= 1 << 7;
} else {
fail("Unexpected");
@@ -732,6 +741,48 @@
}
@Test
+ public void testConvertCheckNotNullWithReturn() {
+ DexItemFactory dexItemFactory = new DexItemFactory();
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(
+ dexItemFactory,
+ reporter,
+ ProguardConfigurationParserOptions.builder()
+ .setEnableExperimentalCheckEnumUnboxed(false)
+ .setEnableExperimentalConvertCheckNotNull(true)
+ .setEnableExperimentalWhyAreYouNotInlining(false)
+ .setEnableTestingOptions(false)
+ .build());
+ String rule = "-convertchecknotnull class C { ** m(**, ...); }";
+ parser.parse(createConfigurationForTesting(ImmutableList.of(rule)));
+ verifyParserEndsCleanly();
+ ProguardConfiguration config = parser.getConfig();
+ assertEquals(1, config.getRules().size());
+ assertTrue(config.getRules().get(0) instanceof ConvertCheckNotNullRule);
+ }
+
+ @Test
+ public void testConvertCheckNotNullWithoutReturn() {
+ DexItemFactory dexItemFactory = new DexItemFactory();
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(
+ dexItemFactory,
+ reporter,
+ ProguardConfigurationParserOptions.builder()
+ .setEnableExperimentalCheckEnumUnboxed(false)
+ .setEnableExperimentalConvertCheckNotNull(true)
+ .setEnableExperimentalWhyAreYouNotInlining(false)
+ .setEnableTestingOptions(false)
+ .build());
+ String rule = "-convertchecknotnull class C { void m(**, ...); }";
+ parser.parse(createConfigurationForTesting(ImmutableList.of(rule)));
+ verifyParserEndsCleanly();
+ ProguardConfiguration config = parser.getConfig();
+ assertEquals(1, config.getRules().size());
+ assertTrue(config.getRules().get(0) instanceof ConvertCheckNotNullRule);
+ }
+
+ @Test
public void parseDontobfuscate() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -992,7 +1043,7 @@
@Test
public void parseKeepdirectories() {
ProguardConfigurationParser parser =
- new ProguardConfigurationParser(new DexItemFactory(), reporter, null, false);
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(KEEPDIRECTORIES));
verifyParserEndsCleanly();
}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
index 8130a6f..b73fc3e 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationTest.java
@@ -88,15 +88,8 @@
? StringUtils.lines(
"[AnotherSub2, debug]: message08", "[AnotherSub2, debug]: message5", "The end")
: StringUtils.lines(
- // TODO(b/133208961): Introduce comparison/meet of assume rules.
- // Itf has side effects for all methods, since we don't compute the meet yet.
- "[Sub1, info]: message00",
"[Base1, debug]: message00",
- "[Sub1, verbose]: message00",
- "[Base2, info]: message08",
"[AnotherSub2, debug]: message08",
- "[AnotherSub2, verbose]: message08",
- // Base2#debug also has side effects.
"[AnotherSub2, debug]: message5",
"The end");
case NON_SPECIFIC_RULES_ALL:
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeReturnsFieldWithSubtypingTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeReturnsFieldWithSubtypingTest.java
new file mode 100644
index 0000000..63ea0c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeReturnsFieldWithSubtypingTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.assumevalues;
+
+import 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.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AssumeReturnsFieldWithSubtypingTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-assumevalues class " + Main.class.getTypeName() + " {",
+ " java.lang.Object getGreeting() return " + Main.class.getTypeName() + ".greeting;",
+ "}",
+ // TODO(b/233828966): Maybe disallow shrinking of this in the first round of shaking.
+ "-keepclassmembers,allowobfuscation class " + Main.class.getTypeName() + "{",
+ " java.lang.String greeting;",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ static class Main {
+
+ static String greeting = System.currentTimeMillis() > 0 ? "Hello world!" : null;
+
+ public static void main(String[] args) {
+ System.out.println(getGreeting());
+ }
+
+ static Object getGreeting() {
+ return "Unexpected";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMissingStaticFieldDiagnosticTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMissingStaticFieldDiagnosticTest.java
new file mode 100644
index 0000000..cc00fac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/AssumeValuesMissingStaticFieldDiagnosticTest.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.assumevalues;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.AssumeValuesMissingStaticFieldDiagnostic;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AssumeValuesMissingStaticFieldDiagnosticTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(Backend.DEX)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-assumevalues class " + Main.class.getTypeName() + " {",
+ " static java.lang.Object get() return Missing.field;",
+ "}")
+ .allowDiagnosticWarningMessages()
+ .setMinApi(AndroidApiLevel.LATEST)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertWarningsMatch(
+ allOf(
+ diagnosticType(AssumeValuesMissingStaticFieldDiagnostic.class),
+ diagnosticMessage(
+ equalTo(
+ "The field Missing.field is used as the return value in an "
+ + "-assumenosideeffects or -assumevalues rule, but no such "
+ + "static field exists.")))));
+ }
+
+ static class Main {
+
+ static Object get() {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
index b2f0964..9b5cc93 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/SynthesizedRulesFromApiLevelTest.java
@@ -303,13 +303,17 @@
"-assumevalues class android.os.Build$VERSION { *; }"
};
- for (String rule : rules) {
+ for (int ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {
+ final int finalRuleIndex = ruleIndex;
+ String rule = rules[ruleIndex];
runTest(
AndroidApiLevel.O_MR1,
AndroidApiLevel.O_MR1,
AndroidApiLevel.O_MR1,
expectedResultForNative(AndroidApiLevel.O_MR1),
- builder -> builder.allowUnusedProguardConfigurationRules(backend == Backend.CF),
+ builder ->
+ builder.allowUnusedProguardConfigurationRules(
+ backend == Backend.CF || finalRuleIndex >= 4),
this::compatCodePresent,
ImmutableList.of(rule),
SynthesizedRule.NOT_PRESENT);
diff --git a/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java b/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java
new file mode 100644
index 0000000..5aad9aa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/convertchecknotnull/ConvertCheckNotNullTest.java
@@ -0,0 +1,172 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.convertchecknotnull;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ConvertCheckNotNullTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .applyIf(
+ parameters.isDexRuntime(),
+ testBuilder -> testBuilder.addLibraryFiles(ToolHelper.getMostRecentAndroidJar()))
+ .addKeepMainRule(Main.class)
+ .addKeepRules(
+ "-convertchecknotnull class " + Main.class.getTypeName() + " {",
+ " void requireNonNullWithoutReturn(**, ...);",
+ " ** requireNonNullWithReturn(**, ...);",
+ "}",
+ "-convertchecknotnull class java.util.Objects {",
+ " ** requireNonNull(**, ...);",
+ "}")
+ .enableExperimentalConvertCheckNotNull()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertEquals(
+ 6,
+ mainMethodSubject
+ .streamInstructions()
+ .filter(
+ CodeMatchers.isInvokeWithTarget(Object.class.getTypeName(), "getClass"))
+ .count());
+
+ assertThat(
+ mainClassSubject.uniqueMethodWithName("requireNonNullWithoutReturn"), isAbsent());
+ assertThat(
+ mainClassSubject.uniqueMethodWithName("requireNonNullWithReturn"), isAbsent());
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(getExpectedOutput());
+ }
+
+ private String getExpectedOutput() {
+ String message4 = "null";
+ String message5 = "null";
+ String message6 = "null";
+ if (parameters.isCfRuntime()) {
+ if (parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK17)) {
+ message4 = "Cannot invoke \"Object.getClass()\" because \"<local3>\" is null";
+ message5 = "Cannot invoke \"Object.getClass()\" because \"<local4>\" is null";
+ message6 = "Cannot invoke \"Object.getClass()\" because \"<local5>\" is null";
+ }
+ } else {
+ if (parameters.getDexRuntimeVersion().isEqualToOneOf(Version.V8_1_0, Version.DEFAULT)) {
+ message4 =
+ message5 = message6 = "Attempt to invoke a virtual method on a null object reference";
+ } else if (parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V5_1_1)) {
+ message4 =
+ message5 =
+ message6 =
+ "Attempt to invoke virtual method 'java.lang.Class"
+ + " java.lang.Object.getClass()' on a null object reference";
+ }
+ }
+ return StringUtils.lines(
+ "Test #1", "Test #2", "Test #3", "Test #4", message4, "Test #5", message5, "Test #6",
+ message6);
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Object p1 = System.currentTimeMillis() >= 0 ? new Object() : null;
+ Object p2 = System.currentTimeMillis() >= 0 ? new Object() : null;
+ Object p3 = System.currentTimeMillis() >= 0 ? new Object() : null;
+ Object p4 = System.currentTimeMillis() >= 0 ? null : new Object();
+ Object p5 = System.currentTimeMillis() >= 0 ? null : new Object();
+ Object p6 = System.currentTimeMillis() >= 0 ? null : new Object();
+
+ System.out.println("Test #1");
+ requireNonNullWithoutReturn(p1, "p1");
+
+ System.out.println("Test #2");
+ Object p2alias = requireNonNullWithReturn(p2, "p2");
+ if (p2alias != p2) {
+ throw new RuntimeException();
+ }
+
+ System.out.println("Test #3");
+ Object p3alias = Objects.requireNonNull(p3, "p3");
+ if (p3alias != p3) {
+ throw new RuntimeException();
+ }
+
+ System.out.println("Test #4");
+ try {
+ requireNonNullWithoutReturn(p4, "p4");
+ } catch (NullPointerException e) {
+ System.out.println(e.getMessage());
+ }
+
+ System.out.println("Test #5");
+ try {
+ Object p5alias = requireNonNullWithReturn(p5, "p5");
+ System.out.println(p5alias);
+ } catch (NullPointerException e) {
+ System.out.println(e.getMessage());
+ }
+
+ System.out.println("Test #6");
+ try {
+ Object p6alias = Objects.requireNonNull(p6, "p6");
+ System.out.println(p6alias);
+ } catch (NullPointerException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+ static void requireNonNullWithoutReturn(Object object, String parameterName) {
+ if (object == null) {
+ throw new NullPointerException("Expected parameter " + parameterName + " to be non-null");
+ }
+ }
+
+ static Object requireNonNullWithReturn(Object object, String parameterName) {
+ if (object == null) {
+ throw new NullPointerException("Expected parameter " + parameterName + " to be non-null");
+ }
+ return object;
+ }
+ }
+}
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 52485b2..b4c6e12 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -6,9 +6,8 @@
import static com.android.tools.r8.references.Reference.classFromTypeName;
import static com.android.tools.r8.transformers.ClassFileTransformer.InnerClassPredicate.always;
import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import static com.android.tools.r8.utils.StringUtils.replaceAll;
-import static org.objectweb.asm.Opcodes.ASM7;
-import static org.objectweb.asm.Opcodes.ASM9;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
@@ -32,7 +31,9 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -81,7 +82,7 @@
final List<MethodTransformer> methodTransformers;
InnerMostClassTransformer(ClassWriter writer, List<MethodTransformer> methodTransformers) {
- super(ASM7, writer);
+ super(ASM_VERSION, writer);
this.methodTransformers = methodTransformers;
}
@@ -331,6 +332,22 @@
});
}
+ public ClassFileTransformer setSuper(Function<String, String> rewrite) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, rewrite.apply(superName), interfaces);
+ }
+ });
+ }
+
public ClassFileTransformer setAccessFlags(Consumer<ClassAccessFlags> fn) {
return addClassTransformer(
new ClassTransformer() {
@@ -423,6 +440,50 @@
});
}
+ public ClassFileTransformer setPermittedSubclasses(
+ Class<?> clazz, Class<?>... permittedSubclasses) {
+ assert !Arrays.asList(permittedSubclasses).contains(clazz);
+ return setMinVersion(CfVm.JDK17)
+ .addClassTransformer(
+ new ClassTransformer() {
+
+ final String name = DescriptorUtils.getBinaryNameFromJavaType(clazz.getTypeName());
+
+ final List<String> permittedSubclassesNames =
+ Arrays.stream(permittedSubclasses)
+ .map(m -> DescriptorUtils.getBinaryNameFromJavaType(m.getTypeName()))
+ .collect(Collectors.toList());
+ String className;
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ className = name;
+ }
+
+ @Override
+ public void visitPermittedSubclass(String permittedSubclass) {
+ // Ignore/remove existing permitted subclasses.
+ }
+
+ @Override
+ public void visitEnd() {
+ if (className.equals(name)) {
+ for (String permittedSubclass : permittedSubclassesNames) {
+ super.visitPermittedSubclass(permittedSubclass);
+ }
+ }
+ super.visitEnd();
+ }
+ });
+ }
+
public ClassFileTransformer unsetAbstract() {
return setAccessFlags(ClassAccessFlags::unsetAbstract);
}
@@ -508,6 +569,11 @@
private ClassFileTransformer setAccessFlags(
MethodReference methodReference, Consumer<MethodAccessFlags> setter) {
+ return setAccessFlags(MethodPredicate.onReference(methodReference), setter);
+ }
+
+ public ClassFileTransformer setAccessFlags(
+ MethodPredicate predicate, Consumer<MethodAccessFlags> setter) {
return addClassTransformer(
new ClassTransformer() {
@@ -519,8 +585,7 @@
|| name.equals(Constants.CLASS_INITIALIZER_NAME);
MethodAccessFlags accessFlags =
MethodAccessFlags.fromCfAccessFlags(access, isConstructor);
- if (name.equals(methodReference.getMethodName())
- && descriptor.equals(methodReference.getMethodDescriptor())) {
+ if (predicate.test(access, name, descriptor, signature, exceptions)) {
setter.accept(accessFlags);
}
return super.visitMethod(
@@ -541,6 +606,12 @@
return (access, otherName, descriptor, signature, exceptions) -> name.equals(otherName);
}
+ static MethodPredicate onReference(MethodReference reference) {
+ return (access, otherName, descriptor, signature, exceptions) ->
+ reference.getMethodName().equals(otherName)
+ && reference.getMethodDescriptor().equals(descriptor);
+ }
+
static boolean testContext(MethodPredicate predicate, MethodContext context) {
MethodReference reference = context.getReference();
return predicate.test(
@@ -604,6 +675,31 @@
});
}
+ public ClassFileTransformer rewriteEnlosingAndNestAttributes(Function<String, String> rewrite) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visitInnerClass(String name, String outerName, String innerName, int access) {
+ super.visitInnerClass(rewrite.apply(name), rewrite.apply(outerName), innerName, access);
+ }
+
+ @Override
+ public void visitOuterClass(String owner, String name, String descriptor) {
+ super.visitOuterClass(rewrite.apply(owner), name, descriptor);
+ }
+
+ @Override
+ public void visitNestMember(String nestMember) {
+ super.visitNestMember(rewrite.apply(nestMember));
+ }
+
+ @Override
+ public void visitNestHost(String nestHost) {
+ super.visitNestHost(rewrite.apply(nestHost));
+ }
+ });
+ }
+
public ClassFileTransformer rewriteEnclosingMethod(
String newOwner, String newName, String newDescriptor) {
return addClassTransformer(
@@ -629,6 +725,18 @@
});
}
+ public ClassFileTransformer removeMethodsCodeAndAnnotations(MethodPredicate predicate) {
+ return addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
+ return predicate.test(access, name, descriptor, signature, exceptions) ? null : mv;
+ }
+ });
+ }
+
public ClassFileTransformer removeMethodsWithName(String nameToRemove) {
return removeMethods(
(access, name, descriptor, signature, exceptions) -> name.equals(nameToRemove));
@@ -869,7 +977,7 @@
@Override
public AnnotationVisitor visitAnnotationDefault() {
- return new AnnotationVisitor(ASM9, super.visitAnnotationDefault()) {
+ return new AnnotationVisitor(ASM_VERSION, super.visitAnnotationDefault()) {
@Override
public void visit(String name, Object value) {
super.visit(name, value);
@@ -921,6 +1029,38 @@
}
@Override
+ public void visitInvokeDynamicInsn(
+ String name,
+ String descriptor,
+ Handle bootstrapMethodHandle,
+ Object... bootstrapMethodArguments) {
+ // This includes the minimal support so that simple lambda are correctly rewritten.
+ // This should be extended based on need if we want to rewrite more complex
+ // invoke-dynamic.
+ Object[] newBootArgs = new Object[bootstrapMethodArguments.length];
+ for (int i = 0; i < bootstrapMethodArguments.length; i++) {
+ Object arg = bootstrapMethodArguments[i];
+ if (arg instanceof Handle) {
+ Handle oldHandle = (Handle) arg;
+ String repl =
+ replaceAll("L" + oldHandle.getOwner() + ";", oldDescriptor, newDescriptor);
+ String newOwner = repl.substring(1, repl.length() - 1);
+ Handle newHandle =
+ new Handle(
+ oldHandle.getTag(),
+ newOwner,
+ oldHandle.getName(),
+ oldHandle.getDesc(),
+ oldHandle.isInterface());
+ newBootArgs[i] = newHandle;
+ } else {
+ newBootArgs[i] = arg;
+ }
+ }
+ super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, newBootArgs);
+ }
+
+ @Override
public void visitTypeInsn(int opcode, String type) {
super.visitTypeInsn(opcode, rewriteASMInternalTypeName(type));
}
@@ -945,7 +1085,7 @@
private MethodVisitor redirectVisitInvokeDynamicInsn(
MethodVisitor visitor, VisitInvokeDynamicInsnCallback callback) {
- return new MethodVisitor(ASM7, visitor) {
+ return new MethodVisitor(ASM_VERSION, visitor) {
@Override
public void visitInvokeDynamicInsn(
String name,
@@ -1036,7 +1176,7 @@
private MethodVisitor redirectVisitFieldInsn(
MethodVisitor visitor, VisitFieldInsnCallback callback) {
- return new MethodVisitor(ASM7, visitor) {
+ return new MethodVisitor(ASM_VERSION, visitor) {
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
callback.visitFieldInsn(opcode, owner, name, descriptor);
@@ -1064,6 +1204,38 @@
});
}
+ public ClassFileTransformer setPredictiveLineNumbering() {
+ return setPredictiveLineNumbering(MethodPredicate.all());
+ }
+
+ public ClassFileTransformer setPredictiveLineNumbering(MethodPredicate predicate) {
+ return setPredictiveLineNumbering(predicate, 0);
+ }
+
+ public ClassFileTransformer setPredictiveLineNumbering(
+ MethodPredicate predicate, int startingLineNumber) {
+ return addMethodTransformer(
+ new MethodTransformer() {
+ private final Map<MethodReference, Integer> lines = new HashMap<>();
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ if (MethodPredicate.testContext(predicate, getContext())) {
+ Integer nextLine =
+ lines.getOrDefault(getContext().getReference(), startingLineNumber);
+ if (nextLine > 0) {
+ super.visitLineNumber(nextLine, start);
+ }
+ // Increment the actual line content by 100 so that each one is clearly distinct
+ // from a PC value for any of the methods.
+ lines.put(getContext().getReference(), nextLine + 100);
+ } else {
+ super.visitLineNumber(line, start);
+ }
+ }
+ });
+ }
+
@FunctionalInterface
private interface VisitMethodInsnCallback {
void visitMethodInsn(
@@ -1072,7 +1244,7 @@
private MethodVisitor redirectVisitMethodInsn(
MethodVisitor visitor, VisitMethodInsnCallback callback) {
- return new MethodVisitor(ASM7, visitor) {
+ return new MethodVisitor(ASM_VERSION, visitor) {
@Override
public void visitMethodInsn(
int opcode, String owner, String name, String descriptor, boolean isInterface) {
@@ -1110,7 +1282,7 @@
private MethodVisitor redirectVisitTypeInsn(
MethodVisitor visitor, VisitTypeInsnCallback callback) {
- return new MethodVisitor(ASM7, visitor) {
+ return new MethodVisitor(ASM_VERSION, visitor) {
@Override
public void visitTypeInsn(int opcode, String type) {
callback.visitTypeInsn(opcode, type);
@@ -1141,7 +1313,7 @@
private MethodVisitor redirectVisitTryCatchBlock(
MethodVisitor visitor, VisitTryCatchBlockCallback callback) {
- return new MethodVisitor(ASM7, visitor) {
+ return new MethodVisitor(ASM_VERSION, visitor) {
@Override
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
callback.visitTryCatchBlock(start, end, handler, type);
@@ -1175,7 +1347,7 @@
}
private MethodVisitor redirectVisitLdcInsn(MethodVisitor visitor, VisitLdcInsnCallback callback) {
- return new MethodVisitor(ASM7, visitor) {
+ return new MethodVisitor(ASM_VERSION, visitor) {
@Override
public void visitLdcInsn(Object value) {
callback.visitLdcInsn(value);
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java
index 885257e..172f334 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassTransformer.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.transformers;
-import static org.objectweb.asm.Opcodes.ASM7;
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import org.objectweb.asm.ClassVisitor;
@@ -14,7 +14,7 @@
*/
public class ClassTransformer extends ClassVisitor {
public ClassTransformer() {
- super(ASM7, null);
+ super(ASM_VERSION, null);
}
// Package internals.
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 55b84f4..a790753 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -225,6 +225,11 @@
}
@Override
+ public List<TypeSubject> getFinalPermittedSubclassAttributes() {
+ throw new Unreachable("Cannot determine PermittedSubclasses attribute of an absent class");
+ }
+
+ @Override
public KmClassSubject getKmClass() {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index a4fb7b4..e3855b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -67,6 +67,11 @@
}
@Override
+ public boolean isNative() {
+ throw new Unreachable("Cannot determine if an absent method is native");
+ }
+
+ @Override
public MethodAccessFlags getAccessFlags() {
throw new Unreachable("Cannot get the access flags for an absent method");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 36a856e..db4985c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -246,6 +246,8 @@
public abstract List<TypeSubject> getFinalNestMembersAttribute();
+ public abstract List<TypeSubject> getFinalPermittedSubclassAttributes();
+
public abstract KmClassSubject getKmClass();
public abstract KmPackageSubject getKmPackage();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 44942ab..402ef93 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -51,6 +51,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -59,6 +60,8 @@
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
public class CodeInspector {
@@ -232,15 +235,26 @@
}
}
- DexAnnotation findAnnotation(String name, DexAnnotationSet annotations) {
- for (DexAnnotation annotation : annotations.annotations) {
- DexType type = annotation.annotation.type;
- String original = mapping == null ? type.toSourceString() : mapping.originalNameOf(type);
- if (original.equals(name)) {
- return annotation;
- }
- }
- return null;
+ public DexAnnotation findAnnotation(DexAnnotationSet annotationSet, String name) {
+ return findAnnotation(
+ annotationSet,
+ annotation -> {
+ DexType type = annotation.annotation.type;
+ String original = mapping == null ? type.toSourceString() : mapping.originalNameOf(type);
+ return original.equals(name);
+ });
+ }
+
+ public DexAnnotation findAnnotation(
+ DexAnnotationSet annotationSet, Predicate<DexAnnotation> predicate) {
+ List<DexAnnotation> annotations = findAnnotations(annotationSet, predicate);
+ assert annotations.size() <= 1;
+ return annotations.isEmpty() ? null : annotations.get(0);
+ }
+
+ public List<DexAnnotation> findAnnotations(
+ DexAnnotationSet annotationSet, Predicate<DexAnnotation> predicate) {
+ return Arrays.stream(annotationSet.annotations).filter(predicate).collect(Collectors.toList());
}
public String getOriginalSignatureAttribute(
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 5b0e184..cb65261 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
import com.android.tools.r8.kotlin.KotlinClassMetadataReader;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
import com.android.tools.r8.naming.MemberNaming;
@@ -370,7 +371,7 @@
assert !name.endsWith("EnclosingClass")
&& !name.endsWith("EnclosingMethod")
&& !name.endsWith("InnerClass");
- DexAnnotation annotation = codeInspector.findAnnotation(name, dexClass.annotations());
+ DexAnnotation annotation = codeInspector.findAnnotation(dexClass.annotations(), name);
return annotation == null
? new AbsentAnnotationSubject()
: new FoundAnnotationSubject(annotation, codeInspector);
@@ -504,6 +505,16 @@
}
@Override
+ public List<TypeSubject> getFinalPermittedSubclassAttributes() {
+ List<TypeSubject> result = new ArrayList<>();
+ for (PermittedSubclassAttribute permittedSubclassAttribute :
+ dexClass.getPermittedSubclassAttributes()) {
+ result.add(new TypeSubject(codeInspector, permittedSubclassAttribute.getPermittedSubclass()));
+ }
+ return result;
+ }
+
+ @Override
public int hashCode() {
int result = codeInspector.hashCode();
result = 31 * result + dexClass.hashCode();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index c2daded..d16221e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -120,7 +120,7 @@
@Override
public AnnotationSubject annotation(String name) {
- DexAnnotation annotation = codeInspector.findAnnotation(name, dexField.annotations());
+ DexAnnotation annotation = codeInspector.findAnnotation(dexField.annotations(), name);
return annotation == null
? new AbsentAnnotationSubject()
: new FoundAnnotationSubject(annotation, codeInspector);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 8a27919..533fa55 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -119,6 +119,11 @@
}
@Override
+ public boolean isNative() {
+ return dexMethod.getAccessFlags().isNative();
+ }
+
+ @Override
public MethodAccessFlags getAccessFlags() {
return dexMethod.getAccessFlags();
}
@@ -372,7 +377,7 @@
@Override
public AnnotationSubject annotation(String name) {
- DexAnnotation annotation = codeInspector.findAnnotation(name, dexMethod.annotations());
+ DexAnnotation annotation = codeInspector.findAnnotation(dexMethod.annotations(), name);
return annotation == null
? new AbsentAnnotationSubject()
: new FoundAnnotationSubject(annotation, codeInspector);
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 db174e0..4a3d5f9 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
@@ -428,7 +428,31 @@
description.appendText("package-private");
}
} else {
- description.appendText(" was absent");
+ description.appendText("absent");
+ }
+ }
+ };
+ }
+
+ public static <T extends MethodSubject> Matcher<T> isNative() {
+ return new TypeSafeMatcher<T>() {
+ @Override
+ public boolean matchesSafely(final T subject) {
+ return subject.isPresent() && subject.isNative();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("native method");
+ }
+
+ @Override
+ public void describeMismatchSafely(final T subject, Description description) {
+ description.appendText("item ").appendValue(subject.getOriginalName()).appendText(" was ");
+ if (subject.isPresent()) {
+ description.appendText("not native");
+ } else {
+ description.appendText("absent");
}
}
};
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index dda3e35..a083636 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -38,6 +38,8 @@
public abstract boolean isVirtual();
+ public abstract boolean isNative();
+
public FoundMethodSubject asFoundMethodSubject() {
return null;
}
diff --git a/third_party/android_jar/libcore_latest.tar.gz.sha1 b/third_party/android_jar/libcore_latest.tar.gz.sha1
new file mode 100644
index 0000000..c300639
--- /dev/null
+++ b/third_party/android_jar/libcore_latest.tar.gz.sha1
@@ -0,0 +1 @@
+f7570bec27977e786b637241778e6cfdadf25f7f
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_17.19.tar.gz.sha1 b/third_party/youtube/youtube.android_17.19.tar.gz.sha1
new file mode 100644
index 0000000..e672542
--- /dev/null
+++ b/third_party/youtube/youtube.android_17.19.tar.gz.sha1
@@ -0,0 +1 @@
+474aefd92152017a7e0ad54020b95c6d5045a0bb
\ No newline at end of file
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index a4a010b..7f3932b 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -39,6 +39,10 @@
parser.add_option('--quiet',
help='disable verbose logging',
default=False)
+ parser.add_option('--sign-before-align',
+ help='Sign the apk before aligning',
+ default=False,
+ action='store_true')
(options, args) = parser.parse_args()
if len(args) != 1:
parser.error('Expected <apk> argument, got: ' + ' '.join(args))
@@ -86,8 +90,9 @@
return apk_utils.align(signed_apk, aligned_apk)
def masseur(
- apk, dex=None, resources=None, out=None, adb_options=None, keystore=None,
- install=False, quiet=False, logging=True):
+ apk, dex=None, resources=None, out=None, adb_options=None,
+ sign_before_align=False, keystore=None, install=False, quiet=False,
+ logging=True):
if not out:
out = os.path.basename(apk)
if not keystore:
@@ -101,11 +106,18 @@
'Signing original APK without modifying dex files', quiet=quiet)
processed_apk = os.path.join(temp, 'processed.apk')
shutil.copyfile(apk, processed_apk)
- signed_apk = sign(
- processed_apk, keystore, temp, quiet=quiet, logging=logging)
- aligned_apk = align(signed_apk, temp, quiet=quiet, logging=logging)
- utils.Print('Writing result to {}'.format(out), quiet=quiet)
- shutil.copyfile(aligned_apk, out)
+ if sign_before_align:
+ signed_apk = sign(
+ processed_apk, keystore, temp, quiet=quiet, logging=logging)
+ aligned_apk = align(signed_apk, temp, quiet=quiet, logging=logging)
+ utils.Print('Writing result to {}'.format(out), quiet=quiet)
+ shutil.copyfile(aligned_apk, out)
+ else:
+ aligned_apk = align(processed_apk, temp, quiet=quiet, logging=logging)
+ signed_apk = sign(
+ aligned_apk, keystore, temp, quiet=quiet, logging=logging)
+ utils.Print('Writing result to {}'.format(out), quiet=quiet)
+ shutil.copyfile(signed_apk, out)
if install:
adb_cmd = ['adb']
if adb_options:
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index 86d3b0f..906284e 100755
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -90,6 +90,7 @@
'--ks-pass', 'pass:' + password,
'--min-sdk-version', '19',
'--out', signed_apk,
+ '--v2-signing-enabled',
unsigned_apk
]
utils.RunCmd(cmd, quiet=quiet, logging=logging)
diff --git a/tools/google-java-format-diff.py b/tools/google-java-format-diff.py
new file mode 100755
index 0000000..c9c3dc3
--- /dev/null
+++ b/tools/google-java-format-diff.py
@@ -0,0 +1,157 @@
+#!/usr/bin/env python3
+#
+#===- google-java-format-diff.py - google-java-format Diff Reformatter -----===#
+#
+# The LLVM Compiler Infrastructure
+#
+# This file is distributed under the University of Illinois Open Source
+# License. See LICENSE.TXT for details.
+#
+#===------------------------------------------------------------------------===#
+
+"""
+google-java-format Diff Reformatter
+============================
+
+This script reads input from a unified diff and reformats all the changed
+lines. This is useful to reformat all the lines touched by a specific patch.
+Example usage for git/svn users:
+
+ git diff -U0 HEAD^ | google-java-format-diff.py -p1 -i
+ svn diff --diff-cmd=diff -x-U0 | google-java-format-diff.py -i
+
+For perforce users:
+
+ P4DIFF="git --no-pager diff --no-index" p4 diff | ./google-java-format-diff.py -i -p7
+
+"""
+
+import argparse
+import difflib
+import re
+import string
+import subprocess
+import io
+import os
+import sys
+from shutil import which
+
+def main():
+ parser = argparse.ArgumentParser(description=
+ 'Reformat changed lines in diff. Without -i '
+ 'option just output the diff that would be '
+ 'introduced.')
+ parser.add_argument('-i', action='store_true', default=False,
+ help='apply edits to files instead of displaying a diff')
+
+ parser.add_argument('-p', metavar='NUM', default=0,
+ help='strip the smallest prefix containing P slashes')
+ parser.add_argument('-regex', metavar='PATTERN', default=None,
+ help='custom pattern selecting file paths to reformat '
+ '(case sensitive, overrides -iregex)')
+ parser.add_argument('-iregex', metavar='PATTERN', default=r'.*\.java',
+ help='custom pattern selecting file paths to reformat '
+ '(case insensitive, overridden by -regex)')
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='be more verbose, ineffective without -i')
+ parser.add_argument('-a', '--aosp', action='store_true',
+ help='use AOSP style instead of Google Style (4-space indentation)')
+ parser.add_argument('--skip-sorting-imports', action='store_true',
+ help='do not fix the import order')
+ parser.add_argument('--skip-removing-unused-imports', action='store_true',
+ help='do not remove ununsed imports')
+ parser.add_argument(
+ '--skip-javadoc-formatting',
+ action='store_true',
+ default=False,
+ help='do not reformat javadoc')
+ parser.add_argument('-b', '--binary', help='path to google-java-format binary')
+ parser.add_argument('--google-java-format-jar', metavar='ABSOLUTE_PATH', default=None,
+ help='use a custom google-java-format jar')
+
+ args = parser.parse_args()
+
+ # Extract changed lines for each file.
+ filename = None
+ lines_by_file = {}
+
+ for line in sys.stdin:
+ match = re.search('^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line)
+ if match:
+ filename = match.group(2)
+ if filename == None:
+ continue
+
+ if args.regex is not None:
+ if not re.match('^%s$' % args.regex, filename):
+ continue
+ else:
+ if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE):
+ continue
+
+ match = re.search('^@@.*\+(\d+)(,(\d+))?', line)
+ if match:
+ start_line = int(match.group(1))
+ line_count = 1
+ if match.group(3):
+ line_count = int(match.group(3))
+ if line_count == 0:
+ continue
+ end_line = start_line + line_count - 1;
+ lines_by_file.setdefault(filename, []).extend(
+ ['-lines', str(start_line) + ':' + str(end_line)])
+
+ if args.binary:
+ base_command = [args.binary]
+ elif args.google_java_format_jar:
+ base_command = [
+ os.path.join(
+ 'third_party', 'openjdk', 'jdk-17', 'linux', 'bin', 'java'),
+ '-jar',
+ '--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
+ '--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED',
+ '--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED',
+ '--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
+ '--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
+ '--add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED',
+ args.google_java_format_jar]
+ else:
+ binary = which('google-java-format') or '/usr/bin/google-java-format'
+ base_command = [binary]
+
+ # Reformat files containing changes in place.
+ for filename, lines in lines_by_file.items():
+ if args.i and args.verbose:
+ print('Formatting', filename)
+ command = base_command[:]
+ if args.i:
+ command.append('-i')
+ if args.aosp:
+ command.append('--aosp')
+ if args.skip_sorting_imports:
+ command.append('--skip-sorting-imports')
+ if args.skip_removing_unused_imports:
+ command.append('--skip-removing-unused-imports')
+ if args.skip_javadoc_formatting:
+ command.append('--skip-javadoc-formatting')
+ command.extend(lines)
+ command.append(filename)
+ p = subprocess.Popen(command, stdout=subprocess.PIPE,
+ stderr=None, stdin=subprocess.PIPE)
+ stdout, stderr = p.communicate()
+ if p.returncode != 0:
+ sys.exit(p.returncode);
+
+ if not args.i:
+ with open(filename) as f:
+ code = f.readlines()
+ formatted_code = io.StringIO(stdout.decode('utf-8')).readlines()
+ diff = difflib.unified_diff(code, formatted_code,
+ filename, filename,
+ '(before formatting)', '(after formatting)')
+ diff_string = ''.join(diff)
+ if len(diff_string) > 0:
+ sys.stdout.write(diff_string)
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/historic_run.py b/tools/historic_run.py
index eaaadd5..75819bb 100755
--- a/tools/historic_run.py
+++ b/tools/historic_run.py
@@ -155,7 +155,7 @@
output_path = options.output or 'build'
time_commit = '%s_%s' % (commit.timestamp, commit.git_hash)
time_commit_path = os.path.join(output_path, time_commit)
- print ' '.join(cmd)
+ print(' '.join(cmd))
if not options.dry_run:
if not os.path.exists(time_commit_path):
os.makedirs(time_commit_path)
@@ -170,7 +170,7 @@
timeout -= 1
if process.poll() is None:
process.kill()
- print "Task timed out"
+ print("Task timed out")
stderr.write("timeout\n")
print('Wrote outputs to: %s' % time_commit_path)
diff --git a/tools/r8_get.py b/tools/r8_get.py
new file mode 100755
index 0000000..cc11e90
--- /dev/null
+++ b/tools/r8_get.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+# Copyright (c) 2022, 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.
+
+import sys
+import argparse
+import compiledump
+
+def parse_arguments():
+ parser = argparse.ArgumentParser(
+ description = 'Helper to fetch r8.jar from cloudstorage.')
+ parser.add_argument(
+ '-v',
+ '--version',
+ help='Version or commit-hash to download '
+ '(e.g., 3.3.50 or 33ae86d80351efc4d632452331d06cb97e42f2a7).',
+ required=True)
+ parser.add_argument(
+ '--outdir',
+ help='Output directory to place the r8.jar in (default cwd).',
+ default=None)
+ return parser.parse_args()
+
+def main():
+ args = parse_arguments()
+ outdir = args.outdir if args.outdir else ''
+ print(compiledump.download_distribution(args.version, True, outdir))
+ return 0
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 50dba8d..8c72939 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -606,6 +606,9 @@
extra_args.append('-Dcom.android.tools.r8.allowTypeErrors=1')
extra_args.append(
'-Dcom.android.tools.r8.disallowClassInlinerGracefulExit=1')
+ if 'system-properties' in values:
+ for system_property in values['system-properties']:
+ extra_args.append(system_property)
if options.debug_agent:
if not options.compiler_build == 'full':
diff --git a/tools/startup/adb_utils.py b/tools/startup/adb_utils.py
old mode 100644
new mode 100755
index b5e2af2..56ad643
--- a/tools/startup/adb_utils.py
+++ b/tools/startup/adb_utils.py
@@ -3,13 +3,15 @@
# 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.
-from enum import Enum
+import argparse
import os
import subprocess
import sys
import threading
import time
+from enum import Enum
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import utils
@@ -351,3 +353,37 @@
issue_key_event('KEYCODE_MENU', device_id)
screen_state = get_screen_state(device_id)
assert screen_state.is_on_and_unlocked(), 'was %s' % screen_state
+
+def parse_options(argv):
+ result = argparse.ArgumentParser(description='Run adb utils.')
+ result.add_argument('--device-id',
+ help='Device id (e.g., emulator-5554).')
+ result.add_argument('--device-pin',
+ help='Device pin code (e.g., 1234)')
+ result.add_argument('--ensure-screen-off',
+ help='Ensure screen off',
+ action='store_true',
+ default=False)
+ result.add_argument('--get-screen-state',
+ help='Get screen state',
+ action='store_true',
+ default=False)
+ result.add_argument('--unlock',
+ help='Unlock device',
+ action='store_true',
+ default=False)
+ options, args = result.parse_known_args(argv)
+ return options, args
+
+def main(argv):
+ (options, args) = parse_options(argv)
+ if options.ensure_screen_off:
+ ensure_screen_off(options.device_id)
+ elif options.get_screen_state:
+ print(get_screen_state(options.device_id))
+ elif options.unlock:
+ unlock(options.device_id, options.device_pin)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/test.py b/tools/test.py
index 27c07de..570da44 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -134,6 +134,9 @@
result.add_option('--no-r8lib', '--no_r8lib',
default=False, action='store_true',
help='Run the tests on R8 full with relocated dependencies.')
+ result.add_option('--no-arttests', '--no_arttests',
+ default=False, action='store_true',
+ help='Do not run the art tests.')
result.add_option('--r8lib-no-deps', '--r8lib_no_deps',
default=False, action='store_true',
help='Run the tests on r8lib without relocated dependencies.')
@@ -345,6 +348,8 @@
gradle_args.append('-Pdesugar_jdk_json_dir=' + desugar_jdk_json_dir)
if desugar_jdk_libs:
gradle_args.append('-Pdesugar_jdk_libs=' + desugar_jdk_libs)
+ if options.no_arttests:
+ gradle_args.append('-Pno_arttests=true')
if options.reset_testing_state:
gradle_args.append('-Ptesting-state')
gradle_args.append('-Preset-testing-state')
diff --git a/tools/upload_to_x20.py b/tools/upload_to_x20.py
index 3a1ef77..e6c293d 100755
--- a/tools/upload_to_x20.py
+++ b/tools/upload_to_x20.py
@@ -21,7 +21,7 @@
return optparse.OptionParser().parse_args()
def uploadFile(filename, dest):
- print 'Uploading to %s' % dest
+ print('Uploading to %s' % dest)
shutil.copyfile(filename, dest)
subprocess.check_call(['chmod', '664', dest])
@@ -29,9 +29,9 @@
(options, args) = parse_options()
assert len(args) == 1
name = args[0]
- print 'Creating archive for %s' % name
+ print('Creating archive for %s' % name)
if not name in os.listdir('.'):
- print 'You must be standing directly below the directory you are uploading'
+ print('You must be standing directly below the directory you are uploading')
return 1
filename = utils.create_archive(name)
sha1 = utils.get_sha1(filename)
@@ -40,7 +40,7 @@
sha1_file = '%s.sha1' % filename
with open(sha1_file, 'w') as output:
output.write(sha1)
- print 'Sha (%s) written to: %s' % (sha1, sha1_file)
+ print('Sha (%s) written to: %s' % (sha1, sha1_file))
if __name__ == '__main__':
sys.exit(Main())
diff --git a/tools/utils.py b/tools/utils.py
index 85fda69..363cc18 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -159,8 +159,18 @@
ANDROID_HOME_ENVIROMENT_NAME, os.path.join(USER_HOME, 'Android', 'Sdk'))
def getAndroidBuildTools():
- version = os.environ.get(ANDROID_TOOLS_VERSION_ENVIRONMENT_NAME, '28.0.3')
- return os.path.join(getAndroidHome(), 'build-tools', version)
+ if ANDROID_TOOLS_VERSION_ENVIRONMENT_NAME in os.environ:
+ version = os.environ.get(ANDROID_TOOLS_VERSION_ENVIRONMENT_NAME)
+ build_tools_dir = os.path.join(getAndroidHome(), 'build-tools', version)
+ assert os.path.exists(build_tools_dir)
+ return build_tools_dir
+ else:
+ versions = ['30.0.3', '30.0.2', '30.0.1', '30.0.0']
+ for version in versions:
+ build_tools_dir = os.path.join(getAndroidHome(), 'build-tools', version)
+ if os.path.exists(build_tools_dir):
+ return build_tools_dir
+ raise Exception('Unable to find Android build-tools')
def is_python3():
return sys.version_info.major == 3
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index 8e1349b..cd47f66 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -7,6 +7,8 @@
ANDROID_H_MR2_API = '13'
ANDROID_L_API = '21'
+ANDROID_M_API = '23'
+
BASE = os.path.join(utils.THIRD_PARTY, 'youtube')
V15_33_BASE = os.path.join(BASE, 'youtube.android_15.33')
@@ -15,7 +17,10 @@
V16_20_BASE = os.path.join(BASE, 'youtube.android_16.20')
V16_20_PREFIX = os.path.join(V16_20_BASE, 'YouTubeRelease')
-LATEST_VERSION = '16.20'
+V17_19_BASE = os.path.join(BASE, 'youtube.android_17.19')
+V17_19_PREFIX = os.path.join(V17_19_BASE, 'YouTubeRelease')
+
+LATEST_VERSION = '17.19'
VERSIONS = {
'15.33': {
@@ -80,6 +85,37 @@
'min-api' : ANDROID_L_API,
}
},
+ '17.19': {
+ 'deploy' : {
+ 'sanitize_libraries': False,
+ 'inputs': ['%s_deploy.jar' % V17_19_PREFIX],
+ 'libraries' : [
+ os.path.join(
+ V17_19_BASE,
+ 'legacy_YouTubeRelease_combined_library_jars_filtered.jar')],
+ 'pgconf': [
+ '%s_proguard.config' % V17_19_PREFIX,
+ '%s_proguard_extra.config' % V17_19_PREFIX,
+ '%s/proguardsettings/YouTubeRelease_proguard.config' % utils.THIRD_PARTY,
+ utils.IGNORE_WARNINGS_RULES],
+ 'min-api' : ANDROID_M_API,
+ 'system-properties': [
+ # TODO(b/235169948): Reenable -checkenumunboxed.
+ # '-Dcom.android.tools.r8.experimental.enablecheckenumunboxed=1',
+ '-Dcom.android.tools.r8.experimental.enableconvertchecknotnull=1'],
+ 'android_java8_libs': {
+ 'config': '%s/desugar_jdk_libs/full_desugar_jdk_libs.json' % V17_19_BASE,
+ # Intentionally not adding desugar_jdk_libs_configuration.jar since it
+ # is part of jdk_libs_to_desugar.jar in YouTube 17.19.
+ 'program': ['%s/desugar_jdk_libs/jdk_libs_to_desugar.jar' % V17_19_BASE],
+ 'library': '%s/android_jar/lib-v33/android.jar' % utils.THIRD_PARTY,
+ 'pgconf': [
+ '%s/desugar_jdk_libs/base.pgcfg' % V17_19_BASE,
+ '%s/desugar_jdk_libs/minify_desugar_jdk_libs.pgcfg' % V17_19_BASE
+ ]
+ }
+ },
+ },
}
def GetLatestVersion():