Merge commit '825c964d58326b0e68b4baa76ebe8786ede4a0eb' into dev-release
Change-Id: I4d0001805440ef2f7301f85b53c0b356b341ece7
diff --git a/.gitignore b/.gitignore
index 3da002f..1c8e875 100644
--- a/.gitignore
+++ b/.gitignore
@@ -164,6 +164,8 @@
third_party/kotlin/kotlin-compiler-1.7.0
third_party/kotlin/kotlin-compiler-1.8.0.tar.gz
third_party/kotlin/kotlin-compiler-1.8.0
+third_party/kotlin/kotlin-compiler-1.9.21.tar.gz
+third_party/kotlin/kotlin-compiler-1.9.21
third_party/kotlin/kotlin-compiler-dev.tar.gz
third_party/kotlin/kotlin-compiler-dev
third_party/kotlinx-coroutines-1.3.6.tar.gz
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
deleted file mode 100644
index dc6f3be..0000000
--- a/buildSrc/build.gradle
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (c) 2016, 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.
-apply plugin: 'java'
-apply plugin: 'idea'
-
-repositories {
- maven {
- url uri('file:../third_party/dependencies')
- }
-}
-
-ext {
- guavaVersion = '31.1-jre'
- asmVersion = '9.5'
- smaliVersion = '3.0.3'
-}
-
-dependencies {
- implementation group: 'com.google.guava', name: 'guava', version: guavaVersion
- implementation group: 'com.android.tools.smali', name: 'smali', version: smaliVersion
- implementation group: 'org.ow2.asm', name: 'asm', version: asmVersion
- implementation group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
- implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
- implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
- implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
-}
-
-sourceCompatibility = JavaVersion.VERSION_1_8
-targetCompatibility = JavaVersion.VERSION_1_8
diff --git a/buildSrc/src/main/java/kotlin/Kotlinc.java b/buildSrc/src/main/java/kotlin/Kotlinc.java
deleted file mode 100644
index efb39aa..0000000
--- a/buildSrc/src/main/java/kotlin/Kotlinc.java
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2017, 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 kotlin;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import org.gradle.api.Action;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.file.FileTree;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.OutputFile;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.process.ExecSpec;
-import utils.Utils;
-
-/**
- * Gradle task to compile Kotlin source files. By default the generated classes target Java 1.6.
- */
-public class Kotlinc extends DefaultTask {
-
- private static final String kotlincExecName = Utils.toolsDir().equals("windows")
- ? "kotlinc.bat"
- : "kotlinc";
-
- private static final Path kotlincExecPath =
- Paths.get(
- "third_party", "kotlin", "kotlin-compiler-1.3.72", "kotlinc", "bin", kotlincExecName);
-
- enum KotlinTargetVersion {
- JAVA_6("1.6"),
- JAVA_8("1.8");
-
- private final String optionName;
-
- KotlinTargetVersion(String optionName) {
- this.optionName = optionName;
- }
- }
-
- private FileTree source;
-
- @OutputFile
- private File destination;
-
- private KotlinTargetVersion targetVersion = KotlinTargetVersion.JAVA_6;
-
- @InputFiles
- public FileCollection getInputFiles() {
- // Note: Using Path object directly causes stack overflow.
- // See: https://github.com/gradle/gradle/issues/1973
- return source.plus(getProject().files(kotlincExecPath.toFile()));
- }
-
- public FileTree getSource() {
- return source;
- }
-
- public void setSource(FileTree source) {
- this.source = source;
- }
-
- public File getDestination() {
- return destination;
- }
-
- public void setDestination(File destination) {
- this.destination = destination;
- }
-
- public KotlinTargetVersion getTargetVersion() {
- return targetVersion;
- }
-
- public void setTargetVersion(KotlinTargetVersion targetVersion) {
- this.targetVersion = targetVersion;
- }
-
- @TaskAction
- public void compile() {
- getProject().exec(new Action<ExecSpec>() {
- @Override
- public void execute(ExecSpec execSpec) {
- try {
- execSpec.setExecutable(kotlincExecPath.toFile());
- execSpec.args("-include-runtime");
- execSpec.args("-nowarn");
- execSpec.args("-jvm-target", targetVersion.optionName);
- execSpec.args("-d", destination.getCanonicalPath());
- execSpec.args(source.getFiles());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
- });
- }
-}
diff --git a/buildSrc/src/main/java/tasks/DownloadDependency.java b/buildSrc/src/main/java/tasks/DownloadDependency.java
deleted file mode 100644
index 6d7a75c..0000000
--- a/buildSrc/src/main/java/tasks/DownloadDependency.java
+++ /dev/null
@@ -1,170 +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 tasks;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-import javax.inject.Inject;
-import org.gradle.api.DefaultTask;
-import org.gradle.api.file.RegularFileProperty;
-import org.gradle.api.provider.Property;
-import org.gradle.api.tasks.InputFiles;
-import org.gradle.api.tasks.OutputDirectory;
-import org.gradle.api.tasks.TaskAction;
-import org.gradle.internal.os.OperatingSystem;
-import org.gradle.workers.WorkAction;
-import org.gradle.workers.WorkParameters;
-import org.gradle.workers.WorkerExecutor;
-
-public class DownloadDependency extends DefaultTask {
-
- public enum Type {
- GOOGLE_STORAGE,
- X20
- }
-
- private final WorkerExecutor workerExecutor;
-
- private Type type;
- private File outputDir;
- private File tarGzFile;
- private File sha1File;
-
- @Inject
- public DownloadDependency(WorkerExecutor workerExecutor) {
- this.workerExecutor = workerExecutor;
- }
-
- public void setType(Type type) {
- this.type = type;
- }
-
- public void setDependency(String dependency) {
- outputDir = new File(dependency);
- tarGzFile = new File(dependency + ".tar.gz");
- sha1File = new File(dependency + ".tar.gz.sha1");
- }
-
- @InputFiles
- public Collection<File> getInputFiles() {
- return Arrays.asList(sha1File, tarGzFile);
- }
-
- @OutputDirectory
- public File getOutputDir() {
- return outputDir;
- }
-
- public File getSha1File() {
- return sha1File;
- }
-
- public File getTarGzFile() {
- return tarGzFile;
- }
-
- @TaskAction
- public void execute() throws IOException, InterruptedException {
- if (!sha1File.exists()) {
- throw new RuntimeException("Missing sha1 file: " + sha1File);
- }
-
- // First run will write the tar.gz file, causing the second run to still be out-of-date.
- // Check if the modification time of the tar is newer than the sha in which case we are done.
- // Also, check the contents of the out directory because gradle appears to create it for us...
- if (outputDir.exists()
- && outputDir.isDirectory()
- && outputDir.list().length > 0
- && tarGzFile.exists()
- && sha1File.lastModified() <= tarGzFile.lastModified()) {
- return;
- }
- if (outputDir.exists() && outputDir.isDirectory()) {
- outputDir.delete();
- }
- workerExecutor
- .noIsolation()
- .submit(
- RunDownload.class,
- parameters -> {
- parameters.getType().set(type);
- parameters.getSha1File().set(sha1File);
- });
- }
-
- public interface RunDownloadParameters extends WorkParameters {
- Property<Type> getType();
-
- RegularFileProperty getSha1File();
- }
-
- public abstract static class RunDownload implements WorkAction<RunDownloadParameters> {
-
- @Override
- public void execute() {
- try {
- RunDownloadParameters parameters = getParameters();
- Type type = parameters.getType().get();
- File sha1File = parameters.getSha1File().getAsFile().get();
- if (type == Type.GOOGLE_STORAGE) {
- downloadFromGoogleStorage(sha1File);
- } else if (type == Type.X20) {
- downloadFromX20(sha1File);
- } else {
- throw new RuntimeException("Unexpected or missing dependency type: " + type);
- }
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private void downloadFromGoogleStorage(File sha1File) throws IOException, InterruptedException {
- List<String> args = Arrays.asList("-n", "-b", "r8-deps", "-s", "-u", sha1File.toString());
- if (OperatingSystem.current().isWindows()) {
- List<String> command = new ArrayList<>();
- command.add("download_from_google_storage.bat");
- command.addAll(args);
- runProcess(new ProcessBuilder().command(command));
- } else {
- runProcess(
- new ProcessBuilder()
- .command("bash", "-c", "download_from_google_storage " + String.join(" ", args)));
- }
- }
-
- private void downloadFromX20(File sha1File) throws IOException, InterruptedException {
- if (OperatingSystem.current().isWindows()) {
- throw new RuntimeException("Downloading from x20 unsupported on windows");
- }
- runProcess(
- new ProcessBuilder()
- .command("bash", "-c", "tools/download_from_x20.py " + sha1File.toString()));
- }
-
- private static void runProcess(ProcessBuilder builder)
- throws IOException, InterruptedException {
- String command = String.join(" ", builder.command());
- Process p = builder.start();
- int exit = p.waitFor();
- if (exit != 0) {
- throw new IOException(
- "Process failed for "
- + command
- + "\n"
- + new BufferedReader(
- new InputStreamReader(p.getErrorStream(), StandardCharsets.UTF_8))
- .lines()
- .collect(Collectors.joining("\n")));
- }
- }
- }
-}
diff --git a/buildSrc/src/main/java/utils/Utils.java b/buildSrc/src/main/java/utils/Utils.java
deleted file mode 100644
index 7f09b81..0000000
--- a/buildSrc/src/main/java/utils/Utils.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (c) 2016, 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 utils;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-public class Utils {
- public static String toolsDir() {
- String osName = System.getProperty("os.name");
- if (osName.equals("Mac OS X")) {
- return "mac";
- } else if (osName.contains("Windows")) {
- return "windows";
- } else {
- return "linux";
- }
- }
-
- public static boolean isWindows() {
- return toolsDir().equals("windows");
- }
-
- public static Path dxExecutable() {
- String dxExecutableName = isWindows() ? "dx.bat" : "dx";
- return Paths.get("tools", toolsDir(), "dx", "bin", dxExecutableName);
- }
-
- public static Path dexMergerExecutable() {
- String executableName = isWindows() ? "dexmerger.bat" : "dexmerger";
- return Paths.get("tools", toolsDir(), "dx", "bin", executableName);
- }
-}
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
index 418e0d5..56562ac 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -707,6 +707,7 @@
"kotlin-compiler-1.6.0",
"kotlin-compiler-1.7.0",
"kotlin-compiler-1.8.0",
+ "kotlin-compiler-1.9.21",
"kotlin-compiler-dev")
.map { ThirdPartyDependency(
it,
diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts
index afa21b3..d7746ac 100644
--- a/d8_r8/main/build.gradle.kts
+++ b/d8_r8/main/build.gradle.kts
@@ -21,7 +21,7 @@
`kotlin-dsl`
id("dependencies-plugin")
id("net.ltgt.errorprone") version "3.0.1"
- id("org.spdx.sbom") version "0.4.0-r8-patch02"
+ id("org.spdx.sbom") version "0.4.0"
}
java {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
new file mode 100644
index 0000000..a175b3d
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/ClassNamePattern.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2023, 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 KeepItemAnnotationGenerator.java.
+// ***********************************************************************************
+
+package com.android.tools.r8.keepanno.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A pattern structure for matching names of classes and interfaces.
+ *
+ * <p>If no properties are set, the default pattern matches any name of a class or interface.
+ */
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface ClassNamePattern {
+
+ /**
+ * Exact simple name of the class or interface.
+ *
+ * <p>For example, the simple name of {@code com.example.MyClass} is {@code MyClass}.
+ *
+ * <p>The default matches any simple name.
+ */
+ String simpleName() default "";
+
+ /**
+ * Exact package name of the class or interface.
+ *
+ * <p>For example, the package of {@code com.example.MyClass} is {@code com.example}.
+ *
+ * <p>The default matches any package.
+ */
+ String packageName() default "";
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
index 579d46f..3f1152e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepBinding.java
@@ -74,6 +74,7 @@
* <ul>
* <li>className
* <li>classConstant
+ * <li>classNamePattern
* <li>instanceOfClassName
* <li>instanceOfClassNameExclusive
* <li>instanceOfClassConstant
@@ -95,6 +96,7 @@
*
* <ul>
* <li>classConstant
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -111,6 +113,7 @@
*
* <ul>
* <li>className
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -121,6 +124,23 @@
Class<?> classConstant() default Object.class;
/**
+ * Define the class-name pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>className
+ * <li>classConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ *
+ * @return The class-name pattern that defines the class.
+ */
+ ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
* Define the instance-of pattern as classes that are instances of the fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining instance-of:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
index 470266b..74b2add 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -36,6 +36,7 @@
* <ul>
* <li>className
* <li>classConstant
+ * <li>classNamePattern
* <li>instanceOfClassName
* <li>instanceOfClassNameExclusive
* <li>instanceOfClassConstant
@@ -57,6 +58,7 @@
*
* <ul>
* <li>classConstant
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -73,6 +75,7 @@
*
* <ul>
* <li>className
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -83,6 +86,23 @@
Class<?> classConstant() default Object.class;
/**
+ * Define the class-name pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>className
+ * <li>classConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ *
+ * @return The class-name pattern that defines the class.
+ */
+ ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
* Define the instance-of pattern as classes that are instances of the fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining instance-of:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
index 7bbc2f0..d60525d 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -133,6 +133,7 @@
* <ul>
* <li>className
* <li>classConstant
+ * <li>classNamePattern
* <li>instanceOfClassName
* <li>instanceOfClassNameExclusive
* <li>instanceOfClassConstant
@@ -154,6 +155,7 @@
*
* <ul>
* <li>classConstant
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -170,6 +172,7 @@
*
* <ul>
* <li>className
+ * <li>classNamePattern
* <li>classFromBinding
* </ul>
*
@@ -180,6 +183,23 @@
Class<?> classConstant() default Object.class;
/**
+ * Define the class-name pattern by reference to a class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining class-name:
+ *
+ * <ul>
+ * <li>className
+ * <li>classConstant
+ * <li>classFromBinding
+ * </ul>
+ *
+ * <p>If none are specified the default is to match any class name.
+ *
+ * @return The class-name pattern that defines the class.
+ */
+ ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
+
+ /**
* Define the instance-of pattern as classes that are instances of the fully qualified class name.
*
* <p>Mutually exclusive with the following other properties defining instance-of:
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
index 1d7e6c6..2fa0ffd 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/TypePattern.java
@@ -29,7 +29,12 @@
*
* <p>For example, {@code "long"} or {@code "java.lang.String"}.
*
- * <p>Mutually exclusive with the property `constant` also defining type-pattern.
+ * <p>Mutually exclusive with the following other properties defining type-pattern:
+ *
+ * <ul>
+ * <li>constant
+ * <li>classNamePattern
+ * </ul>
*/
String name() default "";
@@ -38,7 +43,24 @@
*
* <p>For example, {@code String.class}.
*
- * <p>Mutually exclusive with the property `name` also defining type-pattern.
+ * <p>Mutually exclusive with the following other properties defining type-pattern:
+ *
+ * <ul>
+ * <li>name
+ * <li>classNamePattern
+ * </ul>
*/
Class<?> constant() default Object.class;
+
+ /**
+ * Classes matching the class-name pattern.
+ *
+ * <p>Mutually exclusive with the following other properties defining type-pattern:
+ *
+ * <ul>
+ * <li>name
+ * <li>constant
+ * </ul>
+ */
+ ClassNamePattern classNamePattern() default @ClassNamePattern(simpleName = "");
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java
new file mode 100644
index 0000000..777e99b
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/AnnotationVisitorBase.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2023, 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.keepanno.asm;
+
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Type;
+
+public abstract class AnnotationVisitorBase extends AnnotationVisitor {
+
+ private final ParsingContext parsingContext;
+
+ AnnotationVisitorBase(ParsingContext parsingContext) {
+ super(KeepEdgeReader.ASM_VERSION);
+ this.parsingContext = parsingContext;
+ }
+
+ private String getTypeName(String descriptor) {
+ return Type.getType(descriptor).getClassName();
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ throw parsingContext.error("Unexpected value for property " + name + " with value " + value);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ throw parsingContext.error(
+ "Unexpected annotation for property "
+ + name
+ + " of annotation type "
+ + getTypeName(descriptor));
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ throw parsingContext.error(
+ "Unexpected enum for property "
+ + name
+ + " of enum type "
+ + getTypeName(descriptor)
+ + " with value "
+ + value);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ throw parsingContext.error("Unexpected array for property " + name);
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
new file mode 100644
index 0000000..d6c41ac
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2023, 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.keepanno.asm;
+
+import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
+import com.android.tools.r8.keepanno.asm.ClassSimpleNameParser.ClassSimpleNameProperty;
+import com.android.tools.r8.keepanno.asm.PackageNameParser.PackageNameProperty;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ClassNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
+import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
+import com.google.common.collect.ImmutableList;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+
+public class ClassNameParser
+ extends PropertyParserBase<KeepQualifiedClassNamePattern, ClassNameProperty, ClassNameParser> {
+
+ public ClassNameParser(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
+ public enum ClassNameProperty {
+ PATTERN
+ }
+
+ @Override
+ public ClassNameParser self() {
+ return this;
+ }
+
+ @Override
+ AnnotationVisitor tryPropertyAnnotation(
+ ClassNameProperty property,
+ String name,
+ String descriptor,
+ Consumer<KeepQualifiedClassNamePattern> setValue) {
+ switch (property) {
+ case PATTERN:
+ {
+ AnnotationParsingContext parsingContext =
+ new AnnotationParsingContext(getParsingContext(), descriptor);
+ PackageNameParser packageParser =
+ new PackageNameParser(parsingContext)
+ .setProperty(PackageNameProperty.NAME, ClassNamePattern.packageName);
+ ClassSimpleNameParser nameParser =
+ new ClassSimpleNameParser(parsingContext)
+ .setProperty(ClassSimpleNameProperty.NAME, ClassNamePattern.simpleName);
+ return new ParserVisitor(
+ parsingContext,
+ descriptor,
+ ImmutableList.of(packageParser, nameParser),
+ () ->
+ setValue.accept(
+ KeepQualifiedClassNamePattern.builder()
+ .setPackagePattern(
+ packageParser.getValueOrDefault(KeepPackagePattern.any()))
+ .setNamePattern(
+ nameParser.getValueOrDefault(KeepUnqualfiedClassNamePattern.any()))
+ .build()));
+ }
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassSimpleNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassSimpleNameParser.java
new file mode 100644
index 0000000..d349898
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassSimpleNameParser.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2023, 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.keepanno.asm;
+
+import com.android.tools.r8.keepanno.asm.ClassSimpleNameParser.ClassSimpleNameProperty;
+import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import java.util.function.Consumer;
+
+public class ClassSimpleNameParser
+ extends PropertyParserBase<
+ KeepUnqualfiedClassNamePattern, ClassSimpleNameProperty, ClassSimpleNameParser> {
+
+ public ClassSimpleNameParser(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
+ public enum ClassSimpleNameProperty {
+ NAME
+ }
+
+ @Override
+ public ClassSimpleNameParser self() {
+ return this;
+ }
+
+ @Override
+ public boolean tryProperty(
+ ClassSimpleNameProperty property,
+ String name,
+ Object value,
+ Consumer<KeepUnqualfiedClassNamePattern> setValue) {
+ switch (property) {
+ case NAME:
+ setValue.accept(KeepUnqualfiedClassNamePattern.exact((String) value));
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index 35beddf..00729cb 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.keepanno.ast.AccessVisibility;
import com.android.tools.r8.keepanno.ast.AnnotationConstants;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Binding;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.ClassNamePattern;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Condition;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Constraints;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Edge;
@@ -19,7 +20,7 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.Target;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.TypePattern;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
-import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflection;
+import com.android.tools.r8.keepanno.ast.KeepAnnotationParserException;
import com.android.tools.r8.keepanno.ast.KeepBindingReference;
import com.android.tools.r8.keepanno.ast.KeepBindings;
import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
@@ -31,7 +32,6 @@
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepDeclaration;
import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
@@ -41,6 +41,7 @@
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepItemReference;
import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern;
+import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern.BuilderBase;
import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
@@ -50,10 +51,18 @@
import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
import com.android.tools.r8.keepanno.ast.KeepOptions;
import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
+import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
import com.android.tools.r8.keepanno.ast.KeepPreconditions;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.KeepTarget;
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
+import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.ClassParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.FieldParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.MethodParsingContext;
+import com.android.tools.r8.keepanno.utils.Unimplemented;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -189,6 +198,7 @@
private static class KeepEdgeClassVisitor extends ClassVisitor {
private final Parent<KeepDeclaration> parent;
private String className;
+ private ClassParsingContext parsingContext;
KeepEdgeClassVisitor(Parent<KeepDeclaration> parent) {
super(ASM_VERSION);
@@ -208,7 +218,12 @@
String superName,
String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
- this.className = binaryNameToTypeName(name);
+ className = binaryNameToTypeName(name);
+ parsingContext = new ClassParsingContext(className);
+ }
+
+ private AnnotationParsingContext annotationParsingContext(String descriptor) {
+ return new AnnotationParsingContext(parsingContext, descriptor);
}
@Override
@@ -218,48 +233,66 @@
return null;
}
if (descriptor.equals(Edge.DESCRIPTOR)) {
- return new KeepEdgeVisitor(parent::accept, this::setContext);
+ return new KeepEdgeVisitor(
+ annotationParsingContext(descriptor), parent::accept, this::setContext);
}
if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
KeepClassItemPattern classItem =
KeepClassItemPattern.builder()
.setClassNamePattern(KeepQualifiedClassNamePattern.exact(className))
.build();
- return new UsesReflectionVisitor(parent::accept, this::setContext, classItem);
+ return new UsesReflectionVisitor(
+ annotationParsingContext(descriptor), parent::accept, this::setContext, classItem);
}
if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
- return new ForApiClassVisitor(parent::accept, this::setContext, className);
+ return new ForApiClassVisitor(
+ annotationParsingContext(descriptor), parent::accept, this::setContext, className);
}
if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
|| descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
return new UsedByReflectionClassVisitor(
- descriptor, parent::accept, this::setContext, className);
+ annotationParsingContext(descriptor),
+ descriptor,
+ parent::accept,
+ this::setContext,
+ className);
}
if (descriptor.equals(AnnotationConstants.CheckRemoved.DESCRIPTOR)) {
return new CheckRemovedClassVisitor(
- descriptor, parent::accept, this::setContext, className, KeepCheckKind.REMOVED);
+ annotationParsingContext(descriptor),
+ descriptor,
+ parent::accept,
+ this::setContext,
+ className,
+ KeepCheckKind.REMOVED);
}
if (descriptor.equals(AnnotationConstants.CheckOptimizedOut.DESCRIPTOR)) {
return new CheckRemovedClassVisitor(
- descriptor, parent::accept, this::setContext, className, KeepCheckKind.OPTIMIZED_OUT);
+ annotationParsingContext(descriptor),
+ descriptor,
+ parent::accept,
+ this::setContext,
+ className,
+ KeepCheckKind.OPTIMIZED_OUT);
}
return null;
}
private void setContext(KeepEdgeMetaInfo.Builder builder) {
- builder.setContextFromClassDescriptor(KeepEdgeReaderUtils.javaTypeToDescriptor(className));
+ builder.setContextFromClassDescriptor(
+ KeepEdgeReaderUtils.getDescriptorFromJavaType(className));
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
- return new KeepEdgeMethodVisitor(parent::accept, className, name, descriptor);
+ return new KeepEdgeMethodVisitor(parsingContext, parent::accept, className, name, descriptor);
}
@Override
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
- return new KeepEdgeFieldVisitor(parent::accept, className, name, descriptor);
+ return new KeepEdgeFieldVisitor(parsingContext, parent::accept, className, name, descriptor);
}
}
@@ -268,8 +301,10 @@
private final String className;
private final String methodName;
private final String methodDescriptor;
+ private final MethodParsingContext parsingContext;
KeepEdgeMethodVisitor(
+ ClassParsingContext classParsingContext,
Parent<KeepDeclaration> parent,
String className,
String methodName,
@@ -279,6 +314,8 @@
this.className = className;
this.methodName = methodName;
this.methodDescriptor = methodDescriptor;
+ this.parsingContext =
+ new MethodParsingContext(classParsingContext, methodName, methodDescriptor);
}
private KeepMemberItemPattern createMethodItemContext() {
@@ -304,6 +341,10 @@
.build();
}
+ private AnnotationParsingContext annotationParsingContext(String descriptor) {
+ return new AnnotationParsingContext(parsingContext, descriptor);
+ }
+
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
// Skip any visible annotations as @KeepEdge is not runtime visible.
@@ -311,22 +352,35 @@
return null;
}
if (descriptor.equals(Edge.DESCRIPTOR)) {
- return new KeepEdgeVisitor(parent::accept, this::setContext);
+ return new KeepEdgeVisitor(
+ annotationParsingContext(descriptor), parent::accept, this::setContext);
}
if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
return new UsesReflectionVisitor(
- parent::accept, this::setContext, createMethodItemContext());
+ annotationParsingContext(descriptor),
+ parent::accept,
+ this::setContext,
+ createMethodItemContext());
}
if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
- return new ForApiMemberVisitor(parent::accept, this::setContext, createMethodItemContext());
+ return new ForApiMemberVisitor(
+ annotationParsingContext(descriptor),
+ parent::accept,
+ this::setContext,
+ createMethodItemContext());
}
if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
|| descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
return new UsedByReflectionMemberVisitor(
- descriptor, parent::accept, this::setContext, createMethodItemContext());
+ annotationParsingContext(descriptor),
+ descriptor,
+ parent::accept,
+ this::setContext,
+ createMethodItemContext());
}
if (descriptor.equals(AnnotationConstants.CheckRemoved.DESCRIPTOR)) {
return new CheckRemovedMemberVisitor(
+ annotationParsingContext(descriptor),
descriptor,
parent::accept,
this::setContext,
@@ -335,6 +389,7 @@
}
if (descriptor.equals(AnnotationConstants.CheckOptimizedOut.DESCRIPTOR)) {
return new CheckRemovedMemberVisitor(
+ annotationParsingContext(descriptor),
descriptor,
parent::accept,
this::setContext,
@@ -346,7 +401,7 @@
private void setContext(KeepEdgeMetaInfo.Builder builder) {
builder.setContextFromMethodDescriptor(
- KeepEdgeReaderUtils.javaTypeToDescriptor(className), methodName, methodDescriptor);
+ KeepEdgeReaderUtils.getDescriptorFromJavaType(className), methodName, methodDescriptor);
}
}
@@ -355,14 +410,25 @@
private final String className;
private final String fieldName;
private final String fieldDescriptor;
+ private final FieldParsingContext parsingContext;
KeepEdgeFieldVisitor(
- Parent<KeepEdge> parent, String className, String fieldName, String fieldDescriptor) {
+ ClassParsingContext classParsingContext,
+ Parent<KeepEdge> parent,
+ String className,
+ String fieldName,
+ String fieldDescriptor) {
super(ASM_VERSION);
this.parent = parent;
this.className = className;
this.fieldName = fieldName;
this.fieldDescriptor = fieldDescriptor;
+ this.parsingContext =
+ new FieldParsingContext(classParsingContext, fieldName, fieldDescriptor);
+ }
+
+ private AnnotationParsingContext annotationParsingContext(String descriptor) {
+ return new AnnotationParsingContext(parsingContext, descriptor);
}
private KeepMemberItemPattern createMemberItemContext() {
@@ -380,7 +446,7 @@
private void setContext(KeepEdgeMetaInfo.Builder builder) {
builder.setContextFromFieldDescriptor(
- KeepEdgeReaderUtils.javaTypeToDescriptor(className), fieldName, fieldDescriptor);
+ KeepEdgeReaderUtils.getDescriptorFromJavaType(className), fieldName, fieldDescriptor);
}
@Override
@@ -390,18 +456,30 @@
return null;
}
if (descriptor.equals(Edge.DESCRIPTOR)) {
- return new KeepEdgeVisitor(parent, this::setContext);
+ return new KeepEdgeVisitor(annotationParsingContext(descriptor), parent, this::setContext);
}
if (descriptor.equals(AnnotationConstants.UsesReflection.DESCRIPTOR)) {
- return new UsesReflectionVisitor(parent, this::setContext, createMemberItemContext());
+ return new UsesReflectionVisitor(
+ annotationParsingContext(descriptor),
+ parent,
+ this::setContext,
+ createMemberItemContext());
}
if (descriptor.equals(AnnotationConstants.ForApi.DESCRIPTOR)) {
- return new ForApiMemberVisitor(parent, this::setContext, createMemberItemContext());
+ return new ForApiMemberVisitor(
+ annotationParsingContext(descriptor),
+ parent,
+ this::setContext,
+ createMemberItemContext());
}
if (descriptor.equals(AnnotationConstants.UsedByReflection.DESCRIPTOR)
|| descriptor.equals(AnnotationConstants.UsedByNative.DESCRIPTOR)) {
return new UsedByReflectionMemberVisitor(
- descriptor, parent, this::setContext, createMemberItemContext());
+ annotationParsingContext(descriptor),
+ descriptor,
+ parent,
+ this::setContext,
+ createMemberItemContext());
}
return null;
}
@@ -412,40 +490,6 @@
void accept(T result);
}
- private abstract static class AnnotationVisitorBase extends AnnotationVisitor {
-
- AnnotationVisitorBase() {
- super(ASM_VERSION);
- }
-
- public abstract String getAnnotationName();
-
- private String errorMessagePrefix() {
- return " @" + getAnnotationName() + ": ";
- }
-
- @Override
- public void visit(String name, Object value) {
- throw new KeepEdgeException(
- "Unexpected value in" + errorMessagePrefix() + name + " = " + value);
- }
-
- @Override
- public AnnotationVisitor visitAnnotation(String name, String descriptor) {
- throw new KeepEdgeException("Unexpected annotation in" + errorMessagePrefix() + name);
- }
-
- @Override
- public void visitEnum(String name, String descriptor, String value) {
- throw new KeepEdgeException("Unexpected enum in" + errorMessagePrefix() + name);
- }
-
- @Override
- public AnnotationVisitor visitArray(String name) {
- throw new KeepEdgeException("Unexpected array in" + errorMessagePrefix() + name);
- }
- }
-
private static class UserBindingsHelper {
private final KeepBindings.Builder builder = KeepBindings.builder();
private final Map<String, KeepBindingSymbol> userNames = new HashMap<>();
@@ -474,22 +518,24 @@
}
private static class KeepEdgeVisitor extends AnnotationVisitorBase {
+
+ private final AnnotationParsingContext parsingContext;
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
- KeepEdgeVisitor(Parent<KeepEdge> parent, Consumer<KeepEdgeMetaInfo.Builder> addContext) {
+ KeepEdgeVisitor(
+ AnnotationParsingContext parsingContext,
+ Parent<KeepEdge> parent,
+ Consumer<KeepEdgeMetaInfo.Builder> addContext) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.parent = parent;
addContext.accept(metaInfoBuilder);
}
@Override
- public String getAnnotationName() {
- return "KeepEdge";
- }
-
- @Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
@@ -501,15 +547,15 @@
@Override
public AnnotationVisitor visitArray(String name) {
if (name.equals(Edge.bindings)) {
- return new KeepBindingsVisitor(getAnnotationName(), bindingsHelper);
+ return new KeepBindingsVisitor(parsingContext, bindingsHelper);
}
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
- getAnnotationName(), builder::setPreconditions, bindingsHelper);
+ parsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(Edge.consequences)) {
return new KeepConsequencesVisitor(
- getAnnotationName(), builder::setConsequences, bindingsHelper);
+ parsingContext, builder::setConsequences, bindingsHelper);
}
return super.visitArray(name);
}
@@ -529,6 +575,8 @@
* properties are encountered.
*/
private static class ForApiClassVisitor extends KeepItemVisitorBase {
+
+ private final AnnotationParsingContext parsingContext;
private final String className;
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
@@ -537,7 +585,12 @@
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
ForApiClassVisitor(
- Parent<KeepEdge> parent, Consumer<KeepEdgeMetaInfo.Builder> addContext, String className) {
+ AnnotationParsingContext parsingContext,
+ Parent<KeepEdge> parent,
+ Consumer<KeepEdgeMetaInfo.Builder> addContext,
+ String className) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.className = className;
this.parent = parent;
addContext.accept(metaInfoBuilder);
@@ -553,11 +606,6 @@
}
@Override
- public String getAnnotationName() {
- return ForApi.SIMPLE_NAME;
- }
-
- @Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
@@ -570,7 +618,7 @@
public AnnotationVisitor visitArray(String name) {
if (name.equals(ForApi.additionalTargets)) {
return new KeepConsequencesVisitor(
- getAnnotationName(),
+ parsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -591,7 +639,7 @@
Collection<KeepItemReference> items = getItemsWithoutBinding();
for (KeepItemReference item : items) {
if (item.isBindingReference()) {
- throw new KeepEdgeException("@KeepForApi cannot reference bindings");
+ throw parsingContext.error("cannot reference bindings");
}
KeepClassItemPattern classItemPattern = item.asClassItemPattern();
if (classItemPattern == null) {
@@ -601,13 +649,13 @@
String descriptor = KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className);
String itemDescriptor = classItemPattern.getClassNamePattern().getExactDescriptor();
if (!descriptor.equals(itemDescriptor)) {
- throw new KeepEdgeException("@KeepForApi must reference its class context " + className);
+ throw parsingContext.error("must reference its class context " + className);
}
if (classItemPattern.isMemberItemPattern() && items.size() == 1) {
- throw new KeepEdgeException("@KeepForApi kind must include its class");
+ throw parsingContext.error("kind must include its class");
}
if (!classItemPattern.getInstanceOfPattern().isAny()) {
- throw new KeepEdgeException("@KeepForApi cannot define an 'extends' pattern.");
+ throw parsingContext.error("cannot define an 'extends' pattern.");
}
consequences.addTarget(KeepTarget.builder().setItemReference(item).build());
}
@@ -626,6 +674,8 @@
* <p>When used on a member context the annotation does not allow member related patterns.
*/
private static class ForApiMemberVisitor extends AnnotationVisitorBase {
+
+ private final AnnotationParsingContext parsingContext;
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
@@ -633,9 +683,12 @@
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
ForApiMemberVisitor(
+ AnnotationParsingContext parsingContext,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
KeepMemberItemPattern context) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.parent = parent;
addContext.accept(metaInfoBuilder);
// Create a binding for the context such that the class and member are shared.
@@ -655,11 +708,6 @@
}
@Override
- public String getAnnotationName() {
- return ForApi.SIMPLE_NAME;
- }
-
- @Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
@@ -672,7 +720,7 @@
public AnnotationVisitor visitArray(String name) {
if (name.equals(ForApi.additionalTargets)) {
return new KeepConsequencesVisitor(
- getAnnotationName(),
+ parsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -700,6 +748,8 @@
* properties are encountered.
*/
private static class UsedByReflectionClassVisitor extends KeepItemVisitorBase {
+
+ private final AnnotationParsingContext parsingContext;
private final String annotationDescriptor;
private final String className;
private final Parent<KeepEdge> parent;
@@ -710,17 +760,20 @@
private final OptionsDeclaration optionsDeclaration;
UsedByReflectionClassVisitor(
+ AnnotationParsingContext parsingContext,
String annotationDescriptor,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
String className) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.annotationDescriptor = annotationDescriptor;
this.className = className;
this.parent = parent;
addContext.accept(metaInfoBuilder);
// The class context/holder is the annotated class.
visit(Item.className, className);
- optionsDeclaration = new OptionsDeclaration(getAnnotationName());
+ optionsDeclaration = new OptionsDeclaration(parsingContext);
}
@Override
@@ -729,12 +782,6 @@
}
@Override
- public String getAnnotationName() {
- int sep = annotationDescriptor.lastIndexOf('/');
- return annotationDescriptor.substring(sep + 1, annotationDescriptor.length() - 1);
- }
-
- @Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
@@ -747,11 +794,11 @@
public AnnotationVisitor visitArray(String name) {
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
- getAnnotationName(), builder::setPreconditions, bindingsHelper);
+ parsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
- getAnnotationName(),
+ parsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -775,7 +822,7 @@
for (KeepItemReference item : items) {
if (item.isBindingReference()) {
// TODO(b/248408342): The edge can have preconditions so it should support bindings!
- throw new KeepEdgeException("@" + getAnnotationName() + " cannot reference bindings");
+ throw parsingContext.error("cannot reference bindings");
}
KeepItemPattern itemPattern = item.asItemPattern();
KeepClassItemPattern holderPattern =
@@ -785,15 +832,13 @@
String descriptor = KeepEdgeReaderUtils.getDescriptorFromClassTypeName(className);
String itemDescriptor = holderPattern.getClassNamePattern().getExactDescriptor();
if (!descriptor.equals(itemDescriptor)) {
- throw new KeepEdgeException(
- "@" + getAnnotationName() + " must reference its class context " + className);
+ throw parsingContext.error("must reference its class context " + className);
}
if (itemPattern.isMemberItemPattern() && items.size() == 1) {
- throw new KeepEdgeException("@" + getAnnotationName() + " kind must include its class");
+ throw parsingContext.error("kind must include its class");
}
if (!holderPattern.getInstanceOfPattern().isAny()) {
- throw new KeepEdgeException(
- "@" + getAnnotationName() + " cannot define an 'extends' pattern.");
+ throw parsingContext.error("cannot define an 'extends' pattern.");
}
consequences.addTarget(
KeepTarget.builder()
@@ -816,6 +861,8 @@
* <p>When used on a member context the annotation does not allow member related patterns.
*/
private static class UsedByReflectionMemberVisitor extends AnnotationVisitorBase {
+
+ private final AnnotationParsingContext parsingContext;
private final String annotationDescriptor;
private final Parent<KeepEdge> parent;
private final KeepItemPattern context;
@@ -827,21 +874,18 @@
private final OptionsDeclaration optionsDeclaration;
UsedByReflectionMemberVisitor(
+ AnnotationParsingContext parsingContext,
String annotationDescriptor,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
KeepItemPattern context) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.annotationDescriptor = annotationDescriptor;
this.parent = parent;
this.context = context;
addContext.accept(metaInfoBuilder);
- optionsDeclaration = new OptionsDeclaration(getAnnotationName());
- }
-
- @Override
- public String getAnnotationName() {
- int sep = annotationDescriptor.lastIndexOf('/');
- return annotationDescriptor.substring(sep + 1, annotationDescriptor.length() - 1);
+ optionsDeclaration = new OptionsDeclaration(parsingContext);
}
@Override
@@ -870,11 +914,11 @@
public AnnotationVisitor visitArray(String name) {
if (name.equals(Edge.preconditions)) {
return new KeepPreconditionsVisitor(
- getAnnotationName(), builder::setPreconditions, bindingsHelper);
+ parsingContext, builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
- getAnnotationName(),
+ parsingContext,
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
},
@@ -890,7 +934,7 @@
@Override
public void visitEnd() {
if (kind.isOnlyClass()) {
- throw new KeepEdgeException("@" + getAnnotationName() + " kind must include its member");
+ throw parsingContext.error("kind must include its member");
}
assert context.isMemberItemPattern();
KeepMemberItemPattern memberContext = context.asMemberItemPattern();
@@ -914,18 +958,20 @@
private void validateConsistentKind(KeepMemberPattern memberPattern) {
if (memberPattern.isGeneralMember()) {
- throw new KeepEdgeException("Unexpected general pattern for context.");
+ throw parsingContext.error("Unexpected general pattern for context.");
}
if (memberPattern.isMethod() && !kind.includesMethod()) {
- throw new KeepEdgeException("Kind " + kind + " cannot be use when annotating a method");
+ throw parsingContext.error("Kind " + kind + " cannot be use when annotating a method");
}
if (memberPattern.isField() && !kind.includesField()) {
- throw new KeepEdgeException("Kind " + kind + " cannot be use when annotating a field");
+ throw parsingContext.error("Kind " + kind + " cannot be use when annotating a field");
}
}
}
private static class UsesReflectionVisitor extends AnnotationVisitorBase {
+
+ private final AnnotationParsingContext parsingContext;
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepPreconditions.Builder preconditions = KeepPreconditions.builder();
@@ -933,20 +979,18 @@
private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
UsesReflectionVisitor(
+ AnnotationParsingContext parsingContext,
Parent<KeepEdge> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
KeepItemPattern context) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.parent = parent;
preconditions.addCondition(KeepCondition.builder().setItemPattern(context).build());
addContext.accept(metaInfoBuilder);
}
@Override
- public String getAnnotationName() {
- return UsesReflection.SIMPLE_NAME;
- }
-
- @Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
@@ -959,11 +1003,11 @@
public AnnotationVisitor visitArray(String name) {
if (name.equals(AnnotationConstants.UsesReflection.value)) {
return new KeepConsequencesVisitor(
- getAnnotationName(), builder::setConsequences, bindingsHelper);
+ parsingContext, builder::setConsequences, bindingsHelper);
}
if (name.equals(AnnotationConstants.UsesReflection.additionalPreconditions)) {
return new KeepPreconditionsVisitor(
- getAnnotationName(),
+ parsingContext,
additionalPreconditions -> {
additionalPreconditions.forEach(preconditions::addCondition);
},
@@ -984,52 +1028,44 @@
}
private static class KeepBindingsVisitor extends AnnotationVisitorBase {
- private final String annotationName;
+ private final AnnotationParsingContext parsingContext;
private final UserBindingsHelper helper;
- public KeepBindingsVisitor(String annotationName, UserBindingsHelper helper) {
- this.annotationName = annotationName;
+ public KeepBindingsVisitor(AnnotationParsingContext parsingContext, UserBindingsHelper helper) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.helper = helper;
}
@Override
- public String getAnnotationName() {
- return annotationName;
- }
-
- @Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
if (descriptor.equals(AnnotationConstants.Binding.DESCRIPTOR)) {
- return new KeepBindingVisitor(helper);
+ return new KeepBindingVisitor(parsingContext, helper);
}
return super.visitAnnotation(name, descriptor);
}
}
private static class KeepPreconditionsVisitor extends AnnotationVisitorBase {
- private final String annotationName;
+ private final AnnotationParsingContext parsingContext;
private final Parent<KeepPreconditions> parent;
private final KeepPreconditions.Builder builder = KeepPreconditions.builder();
private final UserBindingsHelper bindingsHelper;
public KeepPreconditionsVisitor(
- String annotationName,
+ AnnotationParsingContext parsingContext,
Parent<KeepPreconditions> parent,
UserBindingsHelper bindingsHelper) {
- this.annotationName = annotationName;
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.parent = parent;
this.bindingsHelper = bindingsHelper;
}
@Override
- public String getAnnotationName() {
- return annotationName;
- }
-
- @Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
if (descriptor.equals(Condition.DESCRIPTOR)) {
- return new KeepConditionVisitor(builder::addCondition, bindingsHelper);
+ return new KeepConditionVisitor(parsingContext, builder::addCondition, bindingsHelper);
}
return super.visitAnnotation(name, descriptor);
}
@@ -1041,27 +1077,25 @@
}
private static class KeepConsequencesVisitor extends AnnotationVisitorBase {
- private final String annotationName;
+ private final AnnotationParsingContext parsingContext;
private final Parent<KeepConsequences> parent;
private final KeepConsequences.Builder builder = KeepConsequences.builder();
private final UserBindingsHelper bindingsHelper;
public KeepConsequencesVisitor(
- String annotationName, Parent<KeepConsequences> parent, UserBindingsHelper bindingsHelper) {
- this.annotationName = annotationName;
+ AnnotationParsingContext parsingContext,
+ Parent<KeepConsequences> parent,
+ UserBindingsHelper bindingsHelper) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.parent = parent;
this.bindingsHelper = bindingsHelper;
}
@Override
- public String getAnnotationName() {
- return annotationName;
- }
-
- @Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
if (descriptor.equals(Target.DESCRIPTOR)) {
- return KeepTargetVisitor.create(builder::addTarget, bindingsHelper);
+ return KeepTargetVisitor.create(parsingContext, builder::addTarget, bindingsHelper);
}
return super.visitAnnotation(name, descriptor);
}
@@ -1075,6 +1109,7 @@
/** Parsing of @CheckRemoved and @CheckOptimizedOut on a class context. */
private static class CheckRemovedClassVisitor extends AnnotationVisitorBase {
+ private final AnnotationParsingContext parsingContext;
private final String annotationDescriptor;
private final Parent<KeepCheck> parent;
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
@@ -1082,11 +1117,14 @@
private final KeepCheckKind kind;
public CheckRemovedClassVisitor(
+ AnnotationParsingContext parsingContext,
String annotationDescriptor,
Parent<KeepCheck> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
String className,
KeepCheckKind kind) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.annotationDescriptor = annotationDescriptor;
this.parent = parent;
this.className = className;
@@ -1095,12 +1133,6 @@
}
@Override
- public String getAnnotationName() {
- int sep = annotationDescriptor.lastIndexOf('/');
- return annotationDescriptor.substring(sep + 1, annotationDescriptor.length() - 1);
- }
-
- @Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
@@ -1113,15 +1145,10 @@
public void visitEnd() {
CheckRemovedClassVisitor superVisitor = this;
KeepItemVisitorBase itemVisitor =
- new KeepItemVisitorBase() {
+ new KeepItemVisitorBase(parsingContext) {
@Override
public UserBindingsHelper getBindingsHelper() {
- throw new KeepEdgeException("Bindings not supported in @" + getAnnotationName());
- }
-
- @Override
- public String getAnnotationName() {
- return superVisitor.getAnnotationName();
+ throw parsingContext.error("bindings not supported");
}
};
itemVisitor.visit(Item.className, className);
@@ -1145,11 +1172,13 @@
private final KeepCheckKind kind;
CheckRemovedMemberVisitor(
+ AnnotationParsingContext parsingContext,
String annotationDescriptor,
Parent<KeepDeclaration> parent,
Consumer<KeepEdgeMetaInfo.Builder> addContext,
KeepItemPattern context,
KeepCheckKind kind) {
+ super(parsingContext);
this.annotationDescriptor = annotationDescriptor;
this.parent = parent;
this.context = context;
@@ -1158,12 +1187,6 @@
}
@Override
- public String getAnnotationName() {
- int sep = annotationDescriptor.lastIndexOf('/');
- return annotationDescriptor.substring(sep + 1, annotationDescriptor.length() - 1);
- }
-
- @Override
public void visit(String name, Object value) {
if (name.equals(Edge.description) && value instanceof String) {
metaInfoBuilder.setDescription((String) value);
@@ -1195,7 +1218,6 @@
}
return true;
}
- ;
abstract T getValue();
@@ -1234,10 +1256,19 @@
}
private abstract static class SingleDeclaration<T> extends Declaration<T> {
+ private final ParsingContext parsingContext;
private String declarationName = null;
private T declarationValue = null;
private AnnotationVisitor declarationVisitor = null;
+ private SingleDeclaration(ParsingContext parsingContext) {
+ this.parsingContext = parsingContext;
+ }
+
+ public ParsingContext getParsingContext() {
+ return parsingContext;
+ }
+
abstract T getDefaultValue();
abstract T parse(String name, Object value);
@@ -1260,7 +1291,8 @@
}
private void error(String name) {
- throw new KeepEdgeException(
+ throw new KeepAnnotationParserException(
+ parsingContext,
"Multiple declarations defining "
+ kind()
+ ": '"
@@ -1321,6 +1353,10 @@
private static class ClassNameDeclaration
extends SingleDeclaration<KeepQualifiedClassNamePattern> {
+ private ClassNameDeclaration(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
@Override
String kind() {
return "class-name";
@@ -1341,10 +1377,24 @@
}
return null;
}
+
+ @Override
+ AnnotationVisitor parseAnnotation(
+ String name, String descriptor, Consumer<KeepQualifiedClassNamePattern> setValue) {
+ if (name.equals(Item.classNamePattern) && descriptor.equals(ClassNamePattern.DESCRIPTOR)) {
+ return new ClassNamePatternVisitor(
+ new AnnotationParsingContext(getParsingContext(), descriptor), setValue);
+ }
+ return super.parseAnnotation(name, descriptor, setValue);
+ }
}
private static class InstanceOfDeclaration extends SingleDeclaration<KeepInstanceOfPattern> {
+ private InstanceOfDeclaration(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
@Override
String kind() {
return "instance-of";
@@ -1397,14 +1447,26 @@
private static class ClassDeclaration extends Declaration<KeepClassItemReference> {
+ private final ParsingContext parsingContext;
private final Supplier<UserBindingsHelper> getBindingsHelper;
private KeepClassItemReference boundClassItemReference = null;
- private final ClassNameDeclaration classNameDeclaration = new ClassNameDeclaration();
- private final InstanceOfDeclaration instanceOfDeclaration = new InstanceOfDeclaration();
+ private final ClassNameDeclaration classNameDeclaration;
+ private final InstanceOfDeclaration instanceOfDeclaration;
+ private final List<Declaration<?>> declarations;
- public ClassDeclaration(Supplier<UserBindingsHelper> getBindingsHelper) {
+ public ClassDeclaration(
+ ParsingContext parsingContext, Supplier<UserBindingsHelper> getBindingsHelper) {
+ this.parsingContext = parsingContext;
this.getBindingsHelper = getBindingsHelper;
+ classNameDeclaration = new ClassNameDeclaration(parsingContext);
+ instanceOfDeclaration = new InstanceOfDeclaration(parsingContext);
+ declarations = ImmutableList.of(classNameDeclaration, instanceOfDeclaration);
+ }
+
+ @Override
+ List<Declaration<?>> declarations() {
+ return declarations;
}
private boolean isBindingReferenceDefined() {
@@ -1417,7 +1479,7 @@
private void checkAllowedDefinitions() {
if (isBindingReferenceDefined() && classPatternsAreDefined()) {
- throw new KeepEdgeException(
+ throw parsingContext.error(
"Cannot reference a class binding and class patterns for a single class item");
}
}
@@ -1429,11 +1491,12 @@
@Override
boolean isDefault() {
- return !isBindingReferenceDefined() && !classPatternsAreDefined();
+ return !isBindingReferenceDefined() && super.isDefault();
}
@Override
KeepClassItemReference getValue() {
+ checkAllowedDefinitions();
if (isBindingReferenceDefined()) {
return boundClassItemReference;
}
@@ -1450,7 +1513,7 @@
public void setBindingReference(KeepClassItemReference bindingReference) {
if (isBindingReferenceDefined()) {
- throw new KeepEdgeException(
+ throw parsingContext.error(
"Cannot reference multiple class bindings for a single class item");
}
this.boundClassItemReference = bindingReference;
@@ -1463,25 +1526,23 @@
setBindingReference(KeepBindingReference.forClass(symbol).toClassItemReference());
return true;
}
- if (classNameDeclaration.tryParse(name, value)) {
- checkAllowedDefinitions();
- return true;
- }
- if (instanceOfDeclaration.tryParse(name, value)) {
- checkAllowedDefinitions();
- return true;
- }
- return false;
+ return super.tryParse(name, value);
}
}
private static class MethodReturnTypeDeclaration
extends SingleDeclaration<KeepMethodReturnTypePattern> {
- private final Supplier<String> annotationName;
+ private final TypeParser typeParser;
- private MethodReturnTypeDeclaration(Supplier<String> annotationName) {
- this.annotationName = annotationName;
+ private MethodReturnTypeDeclaration(ParsingContext parsingContext) {
+ super(parsingContext);
+ typeParser =
+ new TypeParser(parsingContext)
+ .setKind("return type")
+ .enableTypePattern(Item.methodReturnTypePattern)
+ .enableTypeName(Item.methodReturnType)
+ .enableTypeConstant(Item.methodReturnTypeConstant);
}
@Override
@@ -1494,44 +1555,46 @@
return KeepMethodReturnTypePattern.any();
}
- @Override
- KeepMethodReturnTypePattern parse(String name, Object value) {
- if (name.equals(Item.methodReturnType) && value instanceof String) {
- return KeepEdgeReaderUtils.methodReturnTypeFromTypeName((String) value);
+ KeepMethodReturnTypePattern fromType(KeepTypePattern typePattern) {
+ if (typePattern == null) {
+ return null;
}
- if (name.equals(Item.methodReturnTypeConstant) && value instanceof Type) {
- Type type = (Type) value;
- return KeepEdgeReaderUtils.methodReturnTypeFromTypeDescriptor(type.getDescriptor());
+ // Special-case method return types to allow void.
+ String descriptor = typePattern.getDescriptor();
+ if (descriptor.equals("V") || descriptor.equals("Lvoid;")) {
+ return KeepMethodReturnTypePattern.voidType();
}
- return null;
+ return KeepMethodReturnTypePattern.fromType(typePattern);
}
@Override
- AnnotationVisitor parseAnnotation(
+ public KeepMethodReturnTypePattern parse(String name, Object value) {
+ return fromType(typeParser.tryParse(name, value));
+ }
+
+ @Override
+ public AnnotationVisitor parseAnnotation(
String name, String descriptor, Consumer<KeepMethodReturnTypePattern> setValue) {
- if (name.equals(Item.methodReturnTypePattern) && descriptor.equals(TypePattern.DESCRIPTOR)) {
- return new TypePatternVisitor(
- annotationName, t -> setValue.accept(KeepMethodReturnTypePattern.fromType(t)));
- }
- return super.parseAnnotation(name, descriptor, setValue);
+ return typeParser.tryParseAnnotation(name, descriptor, t -> setValue.accept(fromType(t)));
}
}
private static class MethodParametersDeclaration
extends SingleDeclaration<KeepMethodParametersPattern> {
- private final Supplier<String> annotationName;
+ private final ParsingContext parsingContext;
private KeepMethodParametersPattern pattern = null;
- public MethodParametersDeclaration(Supplier<String> annotationName) {
- this.annotationName = annotationName;
+ public MethodParametersDeclaration(ParsingContext parsingContext) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
}
private void setPattern(
KeepMethodParametersPattern pattern, Consumer<KeepMethodParametersPattern> setValue) {
assert setValue != null;
if (this.pattern != null) {
- throw new KeepEdgeException("Cannot declare multiple patterns for the parameter list");
+ throw parsingContext.error("Cannot declare multiple patterns for the parameter list");
}
setValue.accept(pattern);
this.pattern = pattern;
@@ -1556,7 +1619,7 @@
AnnotationVisitor parseArray(String name, Consumer<KeepMethodParametersPattern> setValue) {
if (name.equals(Item.methodParameters)) {
return new StringArrayVisitor(
- annotationName,
+ getParsingContext(),
params -> {
KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder();
for (String param : params) {
@@ -1567,7 +1630,7 @@
}
if (name.equals(Item.methodParameterTypePatterns)) {
return new TypePatternsArrayVisitor(
- annotationName,
+ getParsingContext(),
params -> {
KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder();
for (KeepTypePattern param : params) {
@@ -1581,7 +1644,8 @@
}
private static class MethodDeclaration extends Declaration<KeepMethodPattern> {
- private final Supplier<String> annotationName;
+
+ private final ParsingContext parsingContext;
private KeepMethodAccessPattern.Builder accessBuilder = null;
private KeepMethodPattern.Builder builder = null;
private final MethodReturnTypeDeclaration returnTypeDeclaration;
@@ -1589,10 +1653,10 @@
private final List<Declaration<?>> declarations;
- private MethodDeclaration(Supplier<String> annotationName) {
- this.annotationName = annotationName;
- returnTypeDeclaration = new MethodReturnTypeDeclaration(annotationName);
- parametersDeclaration = new MethodParametersDeclaration(annotationName);
+ private MethodDeclaration(ParsingContext parsingContext) {
+ this.parsingContext = parsingContext;
+ returnTypeDeclaration = new MethodReturnTypeDeclaration(parsingContext);
+ parametersDeclaration = new MethodParametersDeclaration(parsingContext);
declarations = ImmutableList.of(returnTypeDeclaration, parametersDeclaration);
}
@@ -1645,7 +1709,7 @@
AnnotationVisitor tryParseArray(String name) {
if (name.equals(Item.methodAccess)) {
accessBuilder = KeepMethodAccessPattern.builder();
- return new MethodAccessVisitor(annotationName, accessBuilder);
+ return new MethodAccessVisitor(parsingContext, accessBuilder);
}
return super.tryParseArray(name);
}
@@ -1653,10 +1717,16 @@
private static class FieldTypeDeclaration extends SingleDeclaration<KeepFieldTypePattern> {
- private final Supplier<String> annotationName;
+ private final TypeParser typeParser;
- private FieldTypeDeclaration(Supplier<String> annotationName) {
- this.annotationName = annotationName;
+ private FieldTypeDeclaration(ParsingContext parsingContext) {
+ super(parsingContext);
+ this.typeParser =
+ new TypeParser(parsingContext)
+ .setKind("field type")
+ .enableTypePattern(Item.fieldTypePattern)
+ .enableTypeName(Item.fieldType)
+ .enableTypeConstant(Item.fieldTypeConstant);
}
@Override
@@ -1670,39 +1740,33 @@
}
@Override
- KeepFieldTypePattern parse(String name, Object value) {
- if (name.equals(Item.fieldType) && value instanceof String) {
- return KeepFieldTypePattern.fromType(
- KeepEdgeReaderUtils.typePatternFromString((String) value));
- }
- if (name.equals(Item.fieldTypeConstant) && value instanceof Type) {
- String descriptor = ((Type) value).getDescriptor();
- return KeepFieldTypePattern.fromType(KeepTypePattern.fromDescriptor(descriptor));
+ public KeepFieldTypePattern parse(String name, Object value) {
+ KeepTypePattern typePattern = typeParser.tryParse(name, value);
+ if (typePattern != null) {
+ return KeepFieldTypePattern.fromType(typePattern);
}
return null;
}
@Override
- AnnotationVisitor parseAnnotation(
+ public AnnotationVisitor parseAnnotation(
String name, String descriptor, Consumer<KeepFieldTypePattern> setValue) {
- if (name.equals(Item.fieldTypePattern) && descriptor.equals(TypePattern.DESCRIPTOR)) {
- return new TypePatternVisitor(
- annotationName, t -> setValue.accept(KeepFieldTypePattern.fromType(t)));
- }
- return super.parseAnnotation(name, descriptor, setValue);
+ return typeParser.tryParseAnnotation(
+ name, descriptor, t -> setValue.accept(KeepFieldTypePattern.fromType(t)));
}
}
private static class FieldDeclaration extends Declaration<KeepFieldPattern> {
- private final Supplier<String> annotationName;
+
+ private final ParsingContext parsingContext;
private final FieldTypeDeclaration typeDeclaration;
private KeepFieldAccessPattern.Builder accessBuilder = null;
private KeepFieldPattern.Builder builder = null;
private final List<Declaration<?>> declarations;
- public FieldDeclaration(Supplier<String> annotationName) {
- this.annotationName = annotationName;
- typeDeclaration = new FieldTypeDeclaration(annotationName);
+ public FieldDeclaration(ParsingContext parsingContext) {
+ this.parsingContext = parsingContext;
+ typeDeclaration = new FieldTypeDeclaration(parsingContext);
declarations = Collections.singletonList(typeDeclaration);
}
@@ -1752,23 +1816,24 @@
AnnotationVisitor tryParseArray(String name) {
if (name.equals(Item.fieldAccess)) {
accessBuilder = KeepFieldAccessPattern.builder();
- return new FieldAccessVisitor(annotationName, accessBuilder);
+ return new FieldAccessVisitor(parsingContext, accessBuilder);
}
return super.tryParseArray(name);
}
}
private static class MemberDeclaration extends Declaration<KeepMemberPattern> {
- private final Supplier<String> annotationName;
+
+ private final ParsingContext parsingContext;
private KeepMemberAccessPattern.Builder accessBuilder = null;
private final MethodDeclaration methodDeclaration;
private final FieldDeclaration fieldDeclaration;
private final List<Declaration<?>> declarations;
- MemberDeclaration(Supplier<String> annotationName) {
- this.annotationName = annotationName;
- methodDeclaration = new MethodDeclaration(annotationName);
- fieldDeclaration = new FieldDeclaration(annotationName);
+ MemberDeclaration(ParsingContext parsingContext) {
+ this.parsingContext = parsingContext;
+ methodDeclaration = new MethodDeclaration(parsingContext);
+ fieldDeclaration = new FieldDeclaration(parsingContext);
declarations = ImmutableList.of(methodDeclaration, fieldDeclaration);
}
@@ -1793,13 +1858,13 @@
KeepFieldPattern field = fieldDeclaration.getValue();
if (accessBuilder != null) {
if (method != null || field != null) {
- throw new KeepEdgeException(
+ throw parsingContext.error(
"Cannot define common member access as well as field or method pattern");
}
return KeepMemberPattern.memberBuilder().setAccessPattern(accessBuilder.build()).build();
}
if (method != null && field != null) {
- throw new KeepEdgeException("Cannot define both a field and a method pattern");
+ throw parsingContext.error("Cannot define both a field and a method pattern");
}
if (method != null) {
return method;
@@ -1814,37 +1879,42 @@
AnnotationVisitor tryParseArray(String name) {
if (name.equals(Item.memberAccess)) {
accessBuilder = KeepMemberAccessPattern.memberBuilder();
- return new MemberAccessVisitor(annotationName, accessBuilder);
+ return new MemberAccessVisitor(parsingContext, accessBuilder);
}
return super.tryParseArray(name);
}
}
private abstract static class KeepItemVisitorBase extends AnnotationVisitorBase {
+ private final ParsingContext parsingContext;
private String memberBindingReference = null;
private ItemKind kind = null;
- private final ClassDeclaration classDeclaration = new ClassDeclaration(this::getBindingsHelper);
+ private final ClassDeclaration classDeclaration;
private final MemberDeclaration memberDeclaration;
+
public abstract UserBindingsHelper getBindingsHelper();
// Constructed item available once visitEnd has been called.
private KeepItemReference itemReference = null;
- KeepItemVisitorBase() {
- memberDeclaration = new MemberDeclaration(this::getAnnotationName);
+ KeepItemVisitorBase(ParsingContext parsingContext) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
+ classDeclaration = new ClassDeclaration(parsingContext, this::getBindingsHelper);
+ memberDeclaration = new MemberDeclaration(parsingContext);
}
public Collection<KeepItemReference> getItemsWithoutBinding() {
if (itemReference == null) {
- throw new KeepEdgeException("Item reference not finalized. Missing call to visitEnd()");
+ throw parsingContext.error("Item reference not finalized. Missing call to visitEnd()");
}
if (itemReference.isBindingReference()) {
return Collections.singletonList(itemReference);
}
// Kind is only null if item is a "binding reference".
if (kind == null) {
- throw new KeepEdgeException("Unexpected state: unknown kind for an item pattern");
+ throw parsingContext.error("Unexpected state: unknown kind for an item pattern");
}
if (kind.includesClassAndMembers()) {
assert !itemReference.isBindingReference();
@@ -1870,14 +1940,14 @@
public Collection<KeepItemReference> getItemsWithBinding() {
if (itemReference == null) {
- throw new KeepEdgeException("Item reference not finalized. Missing call to visitEnd()");
+ throw parsingContext.error("Item reference not finalized. Missing call to visitEnd()");
}
if (itemReference.isBindingReference()) {
return Collections.singletonList(itemReference);
}
// Kind is only null if item is a "binding reference".
if (kind == null) {
- throw new KeepEdgeException("Unexpected state: unknown kind for an item pattern");
+ throw parsingContext.error("Unexpected state: unknown kind for an item pattern");
}
if (kind.includesClassAndMembers()) {
KeepItemPattern itemPattern = itemReference.asItemPattern();
@@ -1914,7 +1984,7 @@
public KeepItemReference getItemReference() {
if (itemReference == null) {
- throw new KeepEdgeException("Item reference not finalized. Missing call to visitEnd()");
+ throw parsingContext.error("Item reference not finalized. Missing call to visitEnd()");
}
return itemReference;
}
@@ -1981,7 +2051,7 @@
if (!classDeclaration.isDefault()
|| !memberDeclaration.getValue().isNone()
|| kind != null) {
- throw new KeepEdgeException(
+ throw parsingContext.error(
"Cannot define an item explicitly and via a member-binding reference");
}
KeepBindingSymbol symbol = getBindingsHelper().resolveUserBinding(memberBindingReference);
@@ -2003,7 +2073,7 @@
}
if (kind.isOnlyClass() && !memberPattern.isNone()) {
- throw new KeepEdgeException("Item pattern for members is incompatible with kind " + kind);
+ throw parsingContext.error("Item pattern for members is incompatible with kind " + kind);
}
// Refine the member pattern to be as precise as the specified kind.
@@ -2020,8 +2090,7 @@
memberPattern = KeepMethodPattern.allMethods();
} else {
assert memberPattern.isField();
- throw new KeepEdgeException(
- "Item pattern for fields is incompatible with kind " + kind);
+ throw parsingContext.error("Item pattern for fields is incompatible with kind " + kind);
}
}
@@ -2038,7 +2107,7 @@
memberPattern = KeepFieldPattern.allFields();
} else {
assert memberPattern.isMethod();
- throw new KeepEdgeException(
+ throw parsingContext.error(
"Item pattern for methods is incompatible with kind " + kind);
}
}
@@ -2064,10 +2133,13 @@
private static class KeepBindingVisitor extends KeepItemVisitorBase {
+ private final AnnotationParsingContext parsingContext;
private final UserBindingsHelper helper;
private String bindingName;
- public KeepBindingVisitor(UserBindingsHelper helper) {
+ public KeepBindingVisitor(AnnotationParsingContext parsingContext, UserBindingsHelper helper) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.helper = helper;
}
@@ -2077,11 +2149,6 @@
}
@Override
- public String getAnnotationName() {
- return Binding.SIMPLE_NAME;
- }
-
- @Override
public void visit(String name, Object value) {
if (name.equals(Binding.bindingName) && value instanceof String) {
bindingName = (String) value;
@@ -2097,7 +2164,7 @@
// The language currently disallows aliasing bindings, thus a binding cannot directly be
// defined by a reference to another binding.
if (item.isBindingReference()) {
- throw new KeepEdgeException(
+ throw parsingContext.error(
"Invalid binding reference to '"
+ item.asBindingReference()
+ "' in binding definition of '"
@@ -2109,21 +2176,15 @@
}
private static class StringArrayVisitor extends AnnotationVisitorBase {
- private final Supplier<String> annotationName;
private final Consumer<List<String>> fn;
private final List<String> strings = new ArrayList<>();
- public StringArrayVisitor(Supplier<String> annotationName, Consumer<List<String>> fn) {
- this.annotationName = annotationName;
+ public StringArrayVisitor(ParsingContext parsingContext, Consumer<List<String>> fn) {
+ super(parsingContext);
this.fn = fn;
}
@Override
- public String getAnnotationName() {
- return annotationName.get();
- }
-
- @Override
public void visit(String name, Object value) {
if (value instanceof String) {
strings.add((String) value);
@@ -2139,25 +2200,134 @@
}
}
- private static class TypePatternVisitor extends AnnotationVisitorBase {
- private final Supplier<String> annotationName;
- private final Consumer<KeepTypePattern> consumer;
- private KeepTypePattern result = null;
+ private static class ClassSimpleNameDeclaration
+ extends SingleDeclaration<KeepUnqualfiedClassNamePattern> {
- private TypePatternVisitor(
- Supplier<String> annotationName, Consumer<KeepTypePattern> consumer) {
- this.annotationName = annotationName;
- this.consumer = consumer;
+ private ClassSimpleNameDeclaration(ParsingContext parsingContext) {
+ super(parsingContext);
}
@Override
- public String getAnnotationName() {
- return annotationName.get();
+ String kind() {
+ return "class-simple-name";
+ }
+
+ @Override
+ KeepUnqualfiedClassNamePattern getDefaultValue() {
+ return KeepUnqualfiedClassNamePattern.any();
+ }
+
+ @Override
+ KeepUnqualfiedClassNamePattern parse(String name, Object value) {
+ if (name.equals(ClassNamePattern.simpleName) && value instanceof String) {
+ return KeepUnqualfiedClassNamePattern.builder().exact((String) value).build();
+ }
+ return null;
+ }
+ }
+
+ private static class PackageDeclaration extends SingleDeclaration<KeepPackagePattern> {
+
+ private PackageDeclaration(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
+ @Override
+ String kind() {
+ return "package";
+ }
+
+ @Override
+ KeepPackagePattern getDefaultValue() {
+ return KeepPackagePattern.any();
+ }
+
+ @Override
+ KeepPackagePattern parse(String name, Object value) {
+ if (name.equals(ClassNamePattern.packageName) && value instanceof String) {
+ return KeepPackagePattern.builder().exact((String) value).build();
+ }
+ return null;
+ }
+ }
+
+ private static class ClassNamePatternDeclaration
+ extends Declaration<KeepQualifiedClassNamePattern> {
+
+ private final ClassSimpleNameDeclaration nameDeclaration;
+ private final PackageDeclaration packageDeclaration;
+ private final List<Declaration<?>> declarations;
+
+ public ClassNamePatternDeclaration(ParsingContext parsingContext) {
+ nameDeclaration = new ClassSimpleNameDeclaration(parsingContext);
+ packageDeclaration = new PackageDeclaration(parsingContext);
+ declarations = ImmutableList.of(nameDeclaration, packageDeclaration);
+ }
+
+ @Override
+ String kind() {
+ return "class-name";
+ }
+
+ @Override
+ KeepQualifiedClassNamePattern getValue() {
+ if (!packageDeclaration.isDefault() || !nameDeclaration.isDefault()) {
+ return KeepQualifiedClassNamePattern.builder()
+ .setPackagePattern(packageDeclaration.getValue())
+ .setNamePattern(nameDeclaration.getValue())
+ .build();
+ }
+ return null;
+ }
+
+ @Override
+ List<Declaration<?>> declarations() {
+ return declarations;
+ }
+ }
+
+ private static class ClassNamePatternVisitor extends AnnotationVisitorBase {
+
+ private final ClassNamePatternDeclaration declaration;
+ private final Consumer<KeepQualifiedClassNamePattern> setValue;
+
+ public ClassNamePatternVisitor(
+ AnnotationParsingContext parsingContext, Consumer<KeepQualifiedClassNamePattern> setValue) {
+ super(parsingContext);
+ this.setValue = setValue;
+ declaration = new ClassNamePatternDeclaration(parsingContext);
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if (!declaration.tryParse(name, value)) {
+ super.visit(name, value);
+ }
+ }
+
+ @Override
+ public void visitEnd() {
+ if (!declaration.isDefault()) {
+ setValue.accept(declaration.getValue());
+ }
+ super.visitEnd();
+ }
+ }
+
+ private static class TypePatternVisitor extends AnnotationVisitorBase {
+ private final ParsingContext parsingContext;
+ private final Consumer<KeepTypePattern> consumer;
+ private KeepTypePattern result = null;
+
+ private TypePatternVisitor(ParsingContext parsingContext, Consumer<KeepTypePattern> consumer) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
+ this.consumer = consumer;
}
private void setResult(KeepTypePattern result) {
if (this.result != null) {
- throw new KeepEdgeException("Invalid type annotation defining multiple properties.");
+ throw parsingContext.error("Invalid type annotation defining multiple properties.");
}
this.result = result;
}
@@ -2177,31 +2347,45 @@
}
@Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ if (TypePattern.classNamePattern.equals(name)
+ && descriptor.equals(ClassNamePattern.DESCRIPTOR)) {
+ return new ClassNamePatternVisitor(
+ new AnnotationParsingContext(parsingContext, descriptor),
+ p -> {
+ if (p.isExact()) {
+ setResult(KeepTypePattern.fromDescriptor(p.getExactDescriptor()));
+ } else {
+ // TODO(b/248408342): Extend the AST type patterns.
+ throw new Unimplemented("Non-exact class patterns are not implemented yet");
+ }
+ });
+ }
+ return super.visitAnnotation(name, descriptor);
+ }
+
+ @Override
public void visitEnd() {
consumer.accept(result != null ? result : KeepTypePattern.any());
}
}
private static class TypePatternsArrayVisitor extends AnnotationVisitorBase {
- private final Supplier<String> annotationName;
+ private final ParsingContext parsingContext;
private final Consumer<List<KeepTypePattern>> fn;
private final List<KeepTypePattern> patterns = new ArrayList<>();
public TypePatternsArrayVisitor(
- Supplier<String> annotationName, Consumer<List<KeepTypePattern>> fn) {
- this.annotationName = annotationName;
+ ParsingContext parsingContext, Consumer<List<KeepTypePattern>> fn) {
+ super(parsingContext);
+ this.parsingContext = parsingContext;
this.fn = fn;
}
@Override
- public String getAnnotationName() {
- return annotationName.get();
- }
-
- @Override
public AnnotationVisitor visitAnnotation(String unusedName, String descriptor) {
if (TypePattern.DESCRIPTOR.equals(descriptor)) {
- return new TypePatternVisitor(annotationName, patterns::add);
+ return new TypePatternVisitor(parsingContext, patterns::add);
}
return null;
}
@@ -2215,10 +2399,8 @@
private static class OptionsDeclaration extends SingleDeclaration<KeepOptions> {
- private final String annotationName;
-
- public OptionsDeclaration(String annotationName) {
- this.annotationName = annotationName;
+ public OptionsDeclaration(ParsingContext parsingContext) {
+ super(parsingContext);
}
@Override
@@ -2240,17 +2422,17 @@
AnnotationVisitor parseArray(String name, Consumer<KeepOptions> setValue) {
if (name.equals(AnnotationConstants.Target.constraints)) {
return new KeepConstraintsVisitor(
- annotationName,
+ getParsingContext(),
options -> setValue.accept(KeepOptions.disallowBuilder().addAll(options).build()));
}
if (name.equals(AnnotationConstants.Target.disallow)) {
return new KeepOptionsVisitor(
- annotationName,
+ getParsingContext(),
options -> setValue.accept(KeepOptions.disallowBuilder().addAll(options).build()));
}
if (name.equals(AnnotationConstants.Target.allow)) {
return new KeepOptionsVisitor(
- annotationName,
+ getParsingContext(),
options -> setValue.accept(KeepOptions.allowBuilder().addAll(options).build()));
}
return null;
@@ -2260,18 +2442,25 @@
private static class KeepTargetVisitor extends KeepItemVisitorBase {
private final Parent<KeepTarget> parent;
- private final KeepTarget.Builder builder = KeepTarget.builder();
- private final OptionsDeclaration optionsDeclaration =
- new OptionsDeclaration(getAnnotationName());
private final UserBindingsHelper bindingsHelper;
+ private final OptionsDeclaration optionsDeclaration;
+ private final KeepTarget.Builder builder = KeepTarget.builder();
- static KeepTargetVisitor create(Parent<KeepTarget> parent, UserBindingsHelper bindingsHelper) {
- return new KeepTargetVisitor(parent, bindingsHelper);
+ static KeepTargetVisitor create(
+ ParsingContext parsingContext,
+ Parent<KeepTarget> parent,
+ UserBindingsHelper bindingsHelper) {
+ return new KeepTargetVisitor(parsingContext, parent, bindingsHelper);
}
- private KeepTargetVisitor(Parent<KeepTarget> parent, UserBindingsHelper bindingsHelper) {
+ private KeepTargetVisitor(
+ ParsingContext parsingContext,
+ Parent<KeepTarget> parent,
+ UserBindingsHelper bindingsHelper) {
+ super(parsingContext);
this.parent = parent;
this.bindingsHelper = bindingsHelper;
+ optionsDeclaration = new OptionsDeclaration(parsingContext);
}
@Override
@@ -2280,11 +2469,6 @@
}
@Override
- public String getAnnotationName() {
- return Target.SIMPLE_NAME;
- }
-
- @Override
public AnnotationVisitor visitArray(String name) {
AnnotationVisitor visitor = optionsDeclaration.tryParseArray(name);
if (visitor != null) {
@@ -2308,7 +2492,11 @@
private final Parent<KeepCondition> parent;
private final UserBindingsHelper bindingsHelper;
- public KeepConditionVisitor(Parent<KeepCondition> parent, UserBindingsHelper bindingsHelper) {
+ public KeepConditionVisitor(
+ ParsingContext parsingContext,
+ Parent<KeepCondition> parent,
+ UserBindingsHelper bindingsHelper) {
+ super(parsingContext);
this.parent = parent;
this.bindingsHelper = bindingsHelper;
}
@@ -2319,11 +2507,6 @@
}
@Override
- public String getAnnotationName() {
- return Condition.SIMPLE_NAME;
- }
-
- @Override
public void visitEnd() {
super.visitEnd();
parent.accept(KeepCondition.builder().setItemReference(getItemReference()).build());
@@ -2332,21 +2515,16 @@
private static class KeepConstraintsVisitor extends AnnotationVisitorBase {
- private final String annotationName;
private final Parent<Collection<KeepOption>> parent;
private final Set<KeepOption> options = new HashSet<>();
- public KeepConstraintsVisitor(String annotationName, Parent<Collection<KeepOption>> parent) {
- this.annotationName = annotationName;
+ public KeepConstraintsVisitor(
+ ParsingContext parsingContext, Parent<Collection<KeepOption>> parent) {
+ super(parsingContext);
this.parent = parent;
}
@Override
- public String getAnnotationName() {
- return annotationName;
- }
-
- @Override
public void visitEnum(String ignore, String descriptor, String value) {
if (!descriptor.equals(AnnotationConstants.Constraints.DESCRIPTOR)) {
super.visitEnum(ignore, descriptor, value);
@@ -2400,21 +2578,16 @@
private static class KeepOptionsVisitor extends AnnotationVisitorBase {
- private final String annotationName;
private final Parent<Collection<KeepOption>> parent;
private final Set<KeepOption> options = new HashSet<>();
- public KeepOptionsVisitor(String annotationName, Parent<Collection<KeepOption>> parent) {
- this.annotationName = annotationName;
+ public KeepOptionsVisitor(
+ ParsingContext parsingContext, Parent<Collection<KeepOption>> parent) {
+ super(parsingContext);
this.parent = parent;
}
@Override
- public String getAnnotationName() {
- return annotationName;
- }
-
- @Override
public void visitEnum(String ignore, String descriptor, String value) {
if (!descriptor.equals(AnnotationConstants.Option.DESCRIPTOR)) {
super.visitEnum(ignore, descriptor, value);
@@ -2451,20 +2624,13 @@
}
private static class MemberAccessVisitor extends AnnotationVisitorBase {
- private final Supplier<String> annotationName;
- KeepMemberAccessPattern.BuilderBase<?, ?> builder;
+ private KeepMemberAccessPattern.BuilderBase<?, ?> builder;
- public MemberAccessVisitor(
- Supplier<String> annotationName, KeepMemberAccessPattern.BuilderBase<?, ?> builder) {
- this.annotationName = annotationName;
+ public MemberAccessVisitor(ParsingContext parsingContext, BuilderBase<?, ?> builder) {
+ super(parsingContext);
this.builder = builder;
}
- @Override
- public String getAnnotationName() {
- return annotationName.get();
- }
-
static boolean withNormalizedAccessFlag(String flag, BiPredicate<String, Boolean> fn) {
boolean allow = !flag.startsWith(MemberAccess.NEGATION_PREFIX);
return allow
@@ -2523,13 +2689,12 @@
private static class MethodAccessVisitor extends MemberAccessVisitor {
- @SuppressWarnings("HidingField")
- KeepMethodAccessPattern.Builder builder;
+ private KeepMethodAccessPattern.Builder methodAccessBuilder;
public MethodAccessVisitor(
- Supplier<String> annotationName, KeepMethodAccessPattern.Builder builder) {
- super(annotationName, builder);
- this.builder = builder;
+ ParsingContext parsingContext, KeepMethodAccessPattern.Builder builder) {
+ super(parsingContext, builder);
+ this.methodAccessBuilder = builder;
}
@Override
@@ -2543,19 +2708,19 @@
(flag, allow) -> {
switch (flag) {
case MethodAccess.SYNCHRONIZED:
- builder.setSynchronized(allow);
+ methodAccessBuilder.setSynchronized(allow);
return true;
case MethodAccess.BRIDGE:
- builder.setBridge(allow);
+ methodAccessBuilder.setBridge(allow);
return true;
case MethodAccess.NATIVE:
- builder.setNative(allow);
+ methodAccessBuilder.setNative(allow);
return true;
case MethodAccess.ABSTRACT:
- builder.setAbstract(allow);
+ methodAccessBuilder.setAbstract(allow);
return true;
case MethodAccess.STRICT_FP:
- builder.setStrictFp(allow);
+ methodAccessBuilder.setStrictFp(allow);
return true;
default:
return false;
@@ -2570,13 +2735,12 @@
private static class FieldAccessVisitor extends MemberAccessVisitor {
- @SuppressWarnings("HidingField")
- KeepFieldAccessPattern.Builder builder;
+ private KeepFieldAccessPattern.Builder fieldAccessBuilder;
public FieldAccessVisitor(
- Supplier<String> annotationName, KeepFieldAccessPattern.Builder builder) {
- super(annotationName, builder);
- this.builder = builder;
+ ParsingContext parsingContext, KeepFieldAccessPattern.Builder builder) {
+ super(parsingContext, builder);
+ this.fieldAccessBuilder = builder;
}
@Override
@@ -2590,10 +2754,10 @@
(flag, allow) -> {
switch (flag) {
case FieldAccess.VOLATILE:
- builder.setVolatile(allow);
+ fieldAccessBuilder.setVolatile(allow);
return true;
case FieldAccess.TRANSIENT:
- builder.setTransient(allow);
+ fieldAccessBuilder.setTransient(allow);
return true;
default:
return false;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
index d88b010..18e5c02 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
@@ -23,19 +23,55 @@
return "L" + getBinaryNameFromClassTypeName(classTypeName) + ";";
}
+ public static String getJavaTypeFromDescriptor(String descriptor) {
+ if (descriptor.length() == 1) {
+ switch (descriptor.charAt(0)) {
+ case 'Z':
+ return "boolean";
+ case 'B':
+ return "byte";
+ case 'C':
+ return "char";
+ case 'S':
+ return "short";
+ case 'I':
+ return "int";
+ case 'J':
+ return "long";
+ case 'F':
+ return "float";
+ case 'D':
+ return "double";
+ case 'V':
+ return "void";
+ default:
+ throw new IllegalStateException("Unexpected descriptor: " + descriptor);
+ }
+ }
+ if (descriptor.charAt(0) == '[') {
+ return getJavaTypeFromDescriptor(descriptor.substring(1)) + "[]";
+ }
+ if (descriptor.charAt(0) == 'L') {
+ return descriptor.substring(1, descriptor.length() - 1).replace('/', '.');
+ }
+ throw new IllegalStateException("Unexpected descriptor: " + descriptor);
+ }
+
public static KeepTypePattern typePatternFromString(String string) {
if (string.equals("<any>")) {
return KeepTypePattern.any();
}
- return KeepTypePattern.fromDescriptor(javaTypeToDescriptor(string));
+ return KeepTypePattern.fromDescriptor(getDescriptorFromJavaType(string));
}
- public static String javaTypeToDescriptor(String type) {
+ public static String getDescriptorFromJavaType(String type) {
switch (type) {
case "boolean":
return "Z";
case "byte":
return "B";
+ case "char":
+ return "C";
case "short":
return "S";
case "int":
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PackageNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PackageNameParser.java
new file mode 100644
index 0000000..dadb8de
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PackageNameParser.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2023, 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.keepanno.asm;
+
+import com.android.tools.r8.keepanno.asm.PackageNameParser.PackageNameProperty;
+import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import java.util.function.Consumer;
+
+public class PackageNameParser
+ extends PropertyParserBase<KeepPackagePattern, PackageNameProperty, PackageNameParser> {
+
+ public PackageNameParser(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
+ public enum PackageNameProperty {
+ NAME
+ }
+
+ @Override
+ public PackageNameParser self() {
+ return this;
+ }
+
+ @Override
+ public boolean tryProperty(
+ PackageNameProperty property,
+ String name,
+ Object value,
+ Consumer<KeepPackagePattern> setValue) {
+ switch (property) {
+ case NAME:
+ setValue.accept(KeepPackagePattern.exact((String) value));
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
new file mode 100644
index 0000000..77d6087
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ParserVisitor.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2023, 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.keepanno.asm;
+
+import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
+import java.util.Collections;
+import java.util.List;
+import org.objectweb.asm.AnnotationVisitor;
+
+/** Convert parser(s) into an annotation visitor. */
+public class ParserVisitor extends AnnotationVisitorBase {
+
+ private final List<PropertyParser<?, ?, ?>> parsers;
+ private final Runnable onVisitEnd;
+
+ public ParserVisitor(
+ AnnotationParsingContext parsingContext,
+ String annotationDescriptor,
+ List<PropertyParser<?, ?, ?>> parsers,
+ Runnable onVisitEnd) {
+ super(parsingContext);
+ this.parsers = parsers;
+ this.onVisitEnd = onVisitEnd;
+ assert annotationDescriptor.equals(parsingContext.getAnnotationDescriptor());
+ }
+
+ public ParserVisitor(
+ AnnotationParsingContext parsingContext,
+ String annotationDescriptor,
+ PropertyParser<?, ?, ?> declaration,
+ Runnable onVisitEnd) {
+ this(parsingContext, annotationDescriptor, Collections.singletonList(declaration), onVisitEnd);
+ }
+
+ private <T> void ignore(T unused) {}
+
+ @Override
+ public void visit(String name, Object value) {
+ for (PropertyParser<?, ?, ?> parser : parsers) {
+ if (parser.tryParse(name, value, this::ignore)) {
+ return;
+ }
+ }
+ super.visit(name, value);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ for (PropertyParser<?, ?, ?> parser : parsers) {
+ AnnotationVisitor visitor = parser.tryParseArray(name, this::ignore);
+ if (visitor != null) {
+ return visitor;
+ }
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ for (PropertyParser<?, ?, ?> parser : parsers) {
+ if (parser.tryParseEnum(name, descriptor, value, this::ignore)) {
+ return;
+ }
+ }
+ super.visitEnum(name, descriptor, value);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ for (PropertyParser<?, ?, ?> parser : parsers) {
+ AnnotationVisitor visitor = parser.tryParseAnnotation(name, descriptor, this::ignore);
+ if (visitor != null) {
+ return visitor;
+ }
+ }
+ return super.visitAnnotation(name, descriptor);
+ }
+
+ @Override
+ public void visitEnd() {
+ onVisitEnd.run();
+ super.visitEnd();
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java
new file mode 100644
index 0000000..67b181b
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParser.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2023, 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.keepanno.asm;
+
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+
+public interface PropertyParser<T, P, S> {
+
+ S self();
+
+ String kind();
+
+ S setProperty(P property, String name);
+
+ boolean isDeclared();
+
+ default boolean isDefault() {
+ return !isDeclared();
+ }
+
+ T getValue();
+
+ boolean tryParse(String name, Object value, Consumer<T> setValue);
+
+ boolean tryParseEnum(String name, String descriptor, String value, Consumer<T> setValue);
+
+ AnnotationVisitor tryParseArray(String name, Consumer<T> setValue);
+
+ AnnotationVisitor tryParseAnnotation(String name, String descriptor, Consumer<T> setValue);
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
new file mode 100644
index 0000000..108f086
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/PropertyParserBase.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2023, 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.keepanno.asm;
+
+import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+
+/** Special case of a property parser allowing only a single value callback. */
+public abstract class PropertyParserBase<T, P, S> implements PropertyParser<T, P, S> {
+
+ private final ParsingContext parsingContext;
+
+ private String kind;
+ private final Map<String, P> mapping = new HashMap<>();
+ private String resultPropertyName = null;
+ private T resultValue = null;
+
+ protected PropertyParserBase(ParsingContext parsingContext) {
+ this.parsingContext = parsingContext;
+ }
+
+ public ParsingContext getParsingContext() {
+ return parsingContext;
+ }
+
+ boolean tryProperty(P property, String name, Object value, Consumer<T> setValue) {
+ return false;
+ }
+
+ public boolean tryPropertyEnum(
+ P property, String name, String descriptor, String value, Consumer<T> setValue) {
+ return false;
+ }
+
+ AnnotationVisitor tryPropertyArray(P property, String name, Consumer<T> setValue) {
+ return null;
+ }
+
+ AnnotationVisitor tryPropertyAnnotation(
+ P property, String name, String descriptor, Consumer<T> setValue) {
+ return null;
+ }
+
+ private Consumer<T> wrap(String propertyName, Consumer<T> setValue) {
+ return value -> {
+ assert value != null;
+ if (resultPropertyName != null) {
+ assert resultValue != null;
+ error(propertyName);
+ } else {
+ resultPropertyName = propertyName;
+ resultValue = value;
+ setValue.accept(value);
+ }
+ };
+ }
+
+ private void error(String name) {
+ throw new KeepEdgeException(
+ "Multiple properties defining "
+ + kind()
+ + ": '"
+ + resultPropertyName
+ + "' and '"
+ + name
+ + "'");
+ }
+
+ public final boolean isDeclared() {
+ assert (resultPropertyName != null) == (resultValue != null);
+ return resultPropertyName != null;
+ }
+
+ public T getValue() {
+ assert (resultPropertyName != null) == (resultValue != null);
+ return resultValue;
+ }
+
+ public T getValueOrDefault(T defaultValue) {
+ assert (resultPropertyName != null) == (resultValue != null);
+ return isDeclared() ? resultValue : defaultValue;
+ }
+
+ /** Helper for parsing directly. Returns non-null if the property-name triggered parsing. */
+ public final T tryParse(String name, Object value) {
+ boolean triggered = tryParse(name, value, unused -> {});
+ assert triggered == (resultValue != null);
+ return resultValue;
+ }
+
+ public String kind() {
+ return kind != null ? kind : "";
+ }
+
+ public S setKind(String kind) {
+ this.kind = kind;
+ return self();
+ }
+
+ /** Add property parsing for the given property-name. */
+ public S setProperty(P property, String name) {
+ P old = mapping.put(name, property);
+ if (old != null) {
+ throw new IllegalArgumentException("Unexpected attempt to redefine property " + name);
+ }
+ return self();
+ }
+
+ @Override
+ public final boolean tryParse(String name, Object value, Consumer<T> setValue) {
+ P prop = mapping.get(name);
+ if (prop != null) {
+ return tryProperty(prop, name, value, wrap(name, setValue));
+ }
+ return false;
+ }
+
+ @Override
+ public final boolean tryParseEnum(
+ String name, String descriptor, String value, Consumer<T> setValue) {
+ P prop = mapping.get(name);
+ if (prop != null) {
+ return tryPropertyEnum(prop, name, descriptor, value, wrap(name, setValue));
+ }
+ return false;
+ }
+
+ @Override
+ public final AnnotationVisitor tryParseArray(String name, Consumer<T> setValue) {
+ P prop = mapping.get(name);
+ if (prop != null) {
+ return tryPropertyArray(prop, name, wrap(name, setValue));
+ }
+ return null;
+ }
+
+ @Override
+ public final AnnotationVisitor tryParseAnnotation(
+ String name, String descriptor, Consumer<T> setValue) {
+ P prop = mapping.get(name);
+ if (prop != null) {
+ return tryPropertyAnnotation(prop, name, descriptor, wrap(name, setValue));
+ }
+ return null;
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
new file mode 100644
index 0000000..6b11f70
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2023, 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.keepanno.asm;
+
+import com.android.tools.r8.keepanno.asm.ClassNameParser.ClassNameProperty;
+import com.android.tools.r8.keepanno.asm.TypeParser.TypeProperty;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants.TypePattern;
+import com.android.tools.r8.keepanno.ast.KeepTypePattern;
+import com.android.tools.r8.keepanno.ast.ParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
+import com.android.tools.r8.keepanno.utils.Unimplemented;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Type;
+
+public class TypeParser extends PropertyParserBase<KeepTypePattern, TypeProperty, TypeParser> {
+
+ public TypeParser(ParsingContext parsingContext) {
+ super(parsingContext);
+ }
+
+ public enum TypeProperty {
+ SELF_PATTERN,
+ TYPE_NAME,
+ TYPE_CONSTANT,
+ CLASS_NAME_PATTERN
+ }
+
+ public TypeParser enableTypePattern(String propertyName) {
+ return setProperty(TypeProperty.SELF_PATTERN, propertyName);
+ }
+
+ public TypeParser enableTypeName(String propertyName) {
+ return setProperty(TypeProperty.TYPE_NAME, propertyName);
+ }
+
+ public TypeParser enableTypeConstant(String propertyName) {
+ return setProperty(TypeProperty.TYPE_CONSTANT, propertyName);
+ }
+
+ public TypeParser enableTypeClassNamePattern(String propertyName) {
+ return setProperty(TypeProperty.CLASS_NAME_PATTERN, propertyName);
+ }
+
+ @Override
+ public TypeParser self() {
+ return this;
+ }
+
+ @Override
+ public boolean tryProperty(
+ TypeProperty property, String name, Object value, Consumer<KeepTypePattern> setValue) {
+ switch (property) {
+ case TYPE_NAME:
+ setValue.accept(KeepEdgeReaderUtils.typePatternFromString((String) value));
+ return true;
+ case TYPE_CONSTANT:
+ setValue.accept(KeepTypePattern.fromDescriptor(((Type) value).getDescriptor()));
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public AnnotationVisitor tryPropertyAnnotation(
+ TypeProperty property, String name, String descriptor, Consumer<KeepTypePattern> setValue) {
+ switch (property) {
+ case SELF_PATTERN:
+ {
+ AnnotationParsingContext parsingContext =
+ new AnnotationParsingContext(getParsingContext(), descriptor);
+ TypeParser typeParser =
+ new TypeParser(parsingContext)
+ .setKind(kind())
+ .enableTypeName(TypePattern.name)
+ .enableTypeConstant(TypePattern.constant)
+ .enableTypeClassNamePattern(TypePattern.classNamePattern);
+ return new ParserVisitor(
+ parsingContext,
+ descriptor,
+ typeParser,
+ () -> setValue.accept(typeParser.getValueOrDefault(KeepTypePattern.any())));
+ }
+ case CLASS_NAME_PATTERN:
+ {
+ return new ClassNameParser(getParsingContext())
+ .setKind(kind())
+ .tryPropertyAnnotation(
+ ClassNameProperty.PATTERN,
+ name,
+ descriptor,
+ classNamePattern -> {
+ if (classNamePattern.isExact()) {
+ setValue.accept(
+ KeepTypePattern.fromDescriptor(classNamePattern.getExactDescriptor()));
+ } else {
+ // TODO(b/248408342): Extend the AST type patterns.
+ throw new Unimplemented("Non-exact class patterns are not implemented yet");
+ }
+ });
+ }
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
index 1ab1561..13a228b 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/AnnotationConstants.java
@@ -16,7 +16,6 @@
*/
public final class AnnotationConstants {
public static final class Edge {
- public static final String SIMPLE_NAME = "KeepEdge";
public static final String DESCRIPTOR = "Lcom/android/tools/r8/keepanno/annotations/KeepEdge;";
public static final String description = "description";
public static final String bindings = "bindings";
@@ -25,7 +24,6 @@
}
public static final class ForApi {
- public static final String SIMPLE_NAME = "KeepForApi";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/KeepForApi;";
public static final String description = "description";
@@ -34,7 +32,6 @@
}
public static final class UsesReflection {
- public static final String SIMPLE_NAME = "UsesReflection";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/UsesReflection;";
public static final String description = "description";
@@ -43,7 +40,6 @@
}
public static final class UsedByReflection {
- public static final String SIMPLE_NAME = "UsedByReflection";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/UsedByReflection;";
public static final String description = "description";
@@ -52,20 +48,17 @@
}
public static final class UsedByNative {
- public static final String SIMPLE_NAME = "UsedByNative";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/UsedByNative;";
// Content is the same as UsedByReflection.
}
public static final class CheckRemoved {
- public static final String SIMPLE_NAME = "CheckRemoved";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/CheckRemoved;";
}
public static final class CheckOptimizedOut {
- public static final String SIMPLE_NAME = "CheckOptimizedOut";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/CheckOptimizedOut;";
}
@@ -76,6 +69,7 @@
public static final String memberFromBinding = "memberFromBinding";
public static final String className = "className";
public static final String classConstant = "classConstant";
+ public static final String classNamePattern = "classNamePattern";
public static final String instanceOfClassName = "instanceOfClassName";
public static final String instanceOfClassNameExclusive = "instanceOfClassNameExclusive";
public static final String instanceOfClassConstant = "instanceOfClassConstant";
@@ -99,20 +93,17 @@
}
public static final class Binding {
- public static final String SIMPLE_NAME = "KeepBinding";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/KeepBinding;";
public static final String bindingName = "bindingName";
}
public static final class Condition {
- public static final String SIMPLE_NAME = "KeepCondition";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/KeepCondition;";
}
public static final class Target {
- public static final String SIMPLE_NAME = "KeepTarget";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/KeepTarget;";
public static final String kind = "kind";
@@ -122,7 +113,6 @@
}
public static final class Kind {
- public static final String SIMPLE_NAME = "KeepItemKind";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/KeepItemKind;";
public static final String ONLY_CLASS = "ONLY_CLASS";
@@ -135,7 +125,6 @@
}
public static final class Constraints {
- public static final String SIMPLE_NAME = "KeepConstraint";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/KeepConstraint;";
public static final String LOOKUP = "LOOKUP";
@@ -154,7 +143,6 @@
}
public static final class Option {
- public static final String SIMPLE_NAME = "KeepOption";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/KeepOption;";
public static final String SHRINKING = "SHRINKING";
@@ -165,7 +153,6 @@
}
public static final class MemberAccess {
- public static final String SIMPLE_NAME = "MemberAccessFlags";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/MemberAccessFlags;";
public static final String NEGATION_PREFIX = "NON_";
@@ -179,7 +166,6 @@
}
public static final class MethodAccess {
- public static final String SIMPLE_NAME = "MethodAccessFlags";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/MethodAccessFlags;";
public static final String SYNCHRONIZED = "SYNCHRONIZED";
@@ -190,7 +176,6 @@
}
public static final class FieldAccess {
- public static final String SIMPLE_NAME = "FieldAccessFlags";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/FieldAccessFlags;";
public static final String VOLATILE = "VOLATILE";
@@ -198,10 +183,17 @@
}
public static final class TypePattern {
- public static final String SIMPLE_NAME = "TypePattern";
public static final String DESCRIPTOR =
"Lcom/android/tools/r8/keepanno/annotations/TypePattern;";
public static final String name = "name";
public static final String constant = "constant";
+ public static final String classNamePattern = "classNamePattern";
+ }
+
+ public static final class ClassNamePattern {
+ public static final String DESCRIPTOR =
+ "Lcom/android/tools/r8/keepanno/annotations/ClassNamePattern;";
+ public static final String simpleName = "simpleName";
+ public static final String packageName = "packageName";
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationParserException.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationParserException.java
new file mode 100644
index 0000000..d98d410
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepAnnotationParserException.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+public class KeepAnnotationParserException extends KeepEdgeException {
+
+ private final ParsingContext context;
+
+ public KeepAnnotationParserException(ParsingContext context, String message) {
+ super(message);
+ this.context = context;
+ }
+
+ @Override
+ public String getMessage() {
+ return super.getMessage() + getContextAsString();
+ }
+
+ private String getContextAsString() {
+ StringBuilder builder = new StringBuilder();
+ ParsingContext current = context;
+ while (current != null) {
+ builder.append("\n in ").append(current.getContextFrameAsString());
+ current = current.getParentContext();
+ }
+ return builder.toString();
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java
index 75094be..4ec248b 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeMetaInfo.java
@@ -115,12 +115,11 @@
}
@Override
- @SuppressWarnings("EqualsGetClass")
public boolean equals(Object o) {
if (this == o) {
return true;
}
- if (o == null || getClass() != o.getClass()) {
+ if (!(o instanceof KeepEdgeClassContext)) {
return false;
}
KeepEdgeClassContext that = (KeepEdgeClassContext) o;
@@ -154,12 +153,11 @@
}
@Override
- @SuppressWarnings("EqualsGetClass")
public boolean equals(Object o) {
if (this == o) {
return true;
}
- if (o == null || getClass() != o.getClass()) {
+ if (!(o instanceof KeepEdgeMethodContext)) {
return false;
}
KeepEdgeMethodContext that = (KeepEdgeMethodContext) o;
@@ -191,12 +189,11 @@
}
@Override
- @SuppressWarnings("EqualsGetClass")
public boolean equals(Object o) {
if (this == o) {
return true;
}
- if (o == null || getClass() != o.getClass()) {
+ if (!(o instanceof KeepEdgeFieldContext)) {
return false;
}
KeepEdgeFieldContext that = (KeepEdgeFieldContext) o;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
new file mode 100644
index 0000000..d7ce9a4
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2023, 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.keepanno.ast;
+
+import static com.android.tools.r8.keepanno.asm.KeepEdgeReaderUtils.getJavaTypeFromDescriptor;
+
+import org.objectweb.asm.Type;
+
+public abstract class ParsingContext {
+
+ public KeepAnnotationParserException error(String message) {
+ throw new KeepAnnotationParserException(this, message);
+ }
+
+ public abstract String getHolderName();
+
+ public ParsingContext getParentContext() {
+ return null;
+ }
+
+ public abstract String getContextFrameAsString();
+
+ public static class ClassParsingContext extends ParsingContext {
+ private final String className;
+
+ public ClassParsingContext(String className) {
+ this.className = className;
+ }
+
+ @Override
+ public String getHolderName() {
+ return className;
+ }
+
+ @Override
+ public String getContextFrameAsString() {
+ return className;
+ }
+ }
+
+ public abstract static class MemberParsingContext extends ParsingContext {
+ private final ClassParsingContext classContext;
+
+ public MemberParsingContext(ClassParsingContext classContext) {
+ this.classContext = classContext;
+ }
+
+ @Override
+ public String getHolderName() {
+ return classContext.getHolderName();
+ }
+
+ @Override
+ public ParsingContext getParentContext() {
+ return classContext;
+ }
+ }
+
+ public static class MethodParsingContext extends MemberParsingContext {
+
+ private final String methodName;
+ private final String methodDescriptor;
+
+ public MethodParsingContext(
+ ClassParsingContext classContext, String methodName, String methodDescriptor) {
+ super(classContext);
+ this.methodName = methodName;
+ this.methodDescriptor = methodDescriptor;
+ }
+
+ @Override
+ public String getContextFrameAsString() {
+ Type methodType = Type.getMethodType(methodDescriptor);
+ StringBuilder builder = new StringBuilder();
+ builder
+ .append("method ")
+ .append(getJavaTypeFromDescriptor(methodType.getReturnType().getDescriptor()))
+ .append(' ')
+ .append(methodName)
+ .append('(');
+ boolean first = true;
+ for (Type argument : methodType.getArgumentTypes()) {
+ if (first) {
+ first = false;
+ } else {
+ builder.append(", ");
+ }
+ builder.append(getJavaTypeFromDescriptor(argument.getDescriptor()));
+ }
+ return builder.append(')').toString();
+ }
+ }
+
+ public static class FieldParsingContext extends MemberParsingContext {
+
+ private final String fieldName;
+ private final String fieldDescriptor;
+
+ public FieldParsingContext(
+ ClassParsingContext classContext, String fieldName, String fieldDescriptor) {
+ super(classContext);
+ this.fieldName = fieldName;
+ this.fieldDescriptor = fieldDescriptor;
+ }
+
+ @Override
+ public String getContextFrameAsString() {
+ return "field " + getJavaTypeFromDescriptor(fieldDescriptor) + " " + fieldName;
+ }
+ }
+
+ public static class AnnotationParsingContext extends ParsingContext {
+ private final ParsingContext parentContext;
+ private final String annotationDescriptor;
+
+ public AnnotationParsingContext(ParsingContext parentContext, String annotationDescriptor) {
+ this.parentContext = parentContext;
+ this.annotationDescriptor = annotationDescriptor;
+ }
+
+ public String getAnnotationDescriptor() {
+ return annotationDescriptor;
+ }
+
+ private String getSimpleAnnotationName() {
+ int i = annotationDescriptor.lastIndexOf('/') + 1;
+ return annotationDescriptor.substring(Math.max(i, 1), annotationDescriptor.length() - 1);
+ }
+
+ @Override
+ public String getHolderName() {
+ return parentContext.getHolderName();
+ }
+
+ @Override
+ public ParsingContext getParentContext() {
+ return parentContext;
+ }
+
+ @Override
+ public String getContextFrameAsString() {
+ return "@" + getSimpleAnnotationName();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index c67603b..f2c08f3 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -393,6 +393,11 @@
return self();
}
+ /** Get the consumer for receiving the proguard-map content or null if unset. */
+ public StringConsumer getProguardMapConsumer() {
+ return proguardMapConsumer;
+ }
+
/**
* Set an output destination to which partition-map content should be written.
*
diff --git a/src/main/java/com/android/tools/r8/MapConsumerToPartitionMapConsumer.java b/src/main/java/com/android/tools/r8/MapConsumerToPartitionMapConsumer.java
index 2fd47d9..56a5446 100644
--- a/src/main/java/com/android/tools/r8/MapConsumerToPartitionMapConsumer.java
+++ b/src/main/java/com/android/tools/r8/MapConsumerToPartitionMapConsumer.java
@@ -7,12 +7,9 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MapConsumer;
-import com.android.tools.r8.naming.ProguardMapMarkerInfo;
import com.android.tools.r8.retrace.ProguardMapPartitioner;
import com.android.tools.r8.retrace.internal.ProguardMapProducerInternal;
-import com.android.tools.r8.utils.ListUtils;
import java.io.IOException;
-import java.util.List;
public class MapConsumerToPartitionMapConsumer implements MapConsumer {
@@ -26,12 +23,8 @@
@Override
public void accept(
DiagnosticsHandler diagnosticsHandler,
- ProguardMapMarkerInfo makerInfo,
ClassNameMapper classNameMapper) {
try {
- List<String> newPreamble =
- ListUtils.joinNewArrayList(makerInfo.toPreamble(), classNameMapper.getPreamble());
- classNameMapper.setPreamble(newPreamble);
partitionMapConsumer.acceptMappingPartitionMetadata(
ProguardMapPartitioner.builder(diagnosticsHandler)
.setProguardMapProducer(new ProguardMapProducerInternal(classNameMapper))
diff --git a/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
index 5e0445e..8bf6828 100644
--- a/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/classmerging/ClassMergerTreeFixer.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
import com.android.tools.r8.utils.collections.DexMethodSignatureBiMap;
+import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -52,6 +53,8 @@
private final SyntheticArgumentClass syntheticArgumentClass;
private final Map<DexProgramClass, DexType> originalSuperTypes = new IdentityHashMap<>();
+
+ protected final DexMethodSignatureSet keptSignatures = DexMethodSignatureSet.create();
private final DexMethodSignatureBiMap<DexMethodSignature> reservedInterfaceSignatures =
new DexMethodSignatureBiMap<>();
@@ -74,6 +77,7 @@
}
timing.begin("Fixup");
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
+ preprocess();
Collection<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
classes.forEach(this::fixupAttributes);
@@ -94,6 +98,10 @@
public abstract boolean isRunningBeforePrimaryOptimizationPass();
+ public void preprocess() {
+ // Intentionally empty.
+ }
+
public void postprocess() {
// Intentionally empty.
}
@@ -155,9 +163,12 @@
}
private DexEncodedMethod fixupVirtualInterfaceMethod(DexEncodedMethod method) {
- DexMethod originalMethodReference = method.getReference();
+ if (keptSignatures.contains(method)) {
+ return method;
+ }
// Don't process this method if it does not refer to a merge class type.
+ DexMethod originalMethodReference = method.getReference();
boolean referencesMergeClass =
Iterables.any(
originalMethodReference.getReferencedBaseTypes(dexItemFactory),
@@ -264,64 +275,70 @@
DexMethod originalMethodReference = method.getReference();
// Fix all type references in the method prototype.
- DexMethodSignature reservedMethodSignature = newMethodSignatures.get(method);
DexMethod newMethodReference;
- if (reservedMethodSignature != null) {
- newMethodReference = reservedMethodSignature.withHolder(clazz, dexItemFactory);
+ if (keptSignatures.contains(method)) {
+ newMethodReference = method.getReference();
} else {
- newMethodReference = fixupMethodReference(originalMethodReference);
- if (newMethodSignatures.containsValue(newMethodReference.getSignature())) {
- // If the method collides with a direct method on the same class then rename it to a
- // globally
- // fresh name and record the signature.
- if (method.isInstanceInitializer()) {
- // If the method is an instance initializer, then add extra nulls.
- Box<Set<DexType>> usedSyntheticArgumentClasses = new Box<>();
- newMethodReference =
- dexItemFactory.createInstanceInitializerWithFreshProto(
- newMethodReference,
- syntheticArgumentClass.getArgumentClasses(),
- tryMethod -> !newMethodSignatures.containsValue(tryMethod.getSignature()),
- usedSyntheticArgumentClasses::set);
- lensBuilder.addExtraParameters(
- originalMethodReference,
- newMethodReference,
- ExtraUnusedNullParameter.computeExtraUnusedNullParameters(
- originalMethodReference, newMethodReference));
+ DexMethodSignature reservedMethodSignature = newMethodSignatures.get(method);
+ if (reservedMethodSignature != null) {
+ newMethodReference = reservedMethodSignature.withHolder(clazz, dexItemFactory);
+ } else {
+ newMethodReference = fixupMethodReference(originalMethodReference);
+ if (keptSignatures.contains(newMethodReference)
+ || newMethodSignatures.containsValue(newMethodReference.getSignature())) {
+ // If the method collides with a direct method on the same class then rename it to a
+ // globally
+ // fresh name and record the signature.
+ if (method.isInstanceInitializer()) {
+ // If the method is an instance initializer, then add extra nulls.
+ Box<Set<DexType>> usedSyntheticArgumentClasses = new Box<>();
+ newMethodReference =
+ dexItemFactory.createInstanceInitializerWithFreshProto(
+ newMethodReference,
+ syntheticArgumentClass.getArgumentClasses(),
+ tryMethod -> !newMethodSignatures.containsValue(tryMethod.getSignature()),
+ usedSyntheticArgumentClasses::set);
+ lensBuilder.addExtraParameters(
+ originalMethodReference,
+ newMethodReference,
+ ExtraUnusedNullParameter.computeExtraUnusedNullParameters(
+ originalMethodReference, newMethodReference));
- // Amend the art profile collection.
- if (usedSyntheticArgumentClasses.isSet()) {
- Set<DexMethod> previousMethodReferences =
- lensBuilder.getOriginalMethodReferences(originalMethodReference);
- if (previousMethodReferences.isEmpty()) {
- profileCollectionAdditions.applyIfContextIsInProfile(
- originalMethodReference,
- additionsBuilder ->
- usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
- } else {
- for (DexMethod previousMethodReference : previousMethodReferences) {
+ // Amend the art profile collection.
+ if (usedSyntheticArgumentClasses.isSet()) {
+ Set<DexMethod> previousMethodReferences =
+ lensBuilder.getOriginalMethodReferences(originalMethodReference);
+ if (previousMethodReferences.isEmpty()) {
profileCollectionAdditions.applyIfContextIsInProfile(
- previousMethodReference,
+ originalMethodReference,
additionsBuilder ->
usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
+ } else {
+ for (DexMethod previousMethodReference : previousMethodReferences) {
+ profileCollectionAdditions.applyIfContextIsInProfile(
+ previousMethodReference,
+ additionsBuilder ->
+ usedSyntheticArgumentClasses.get().forEach(additionsBuilder::addRule));
+ }
}
}
+ } else {
+ newMethodReference =
+ dexItemFactory.createFreshMethodNameWithoutHolder(
+ newMethodReference.getName().toSourceString(),
+ newMethodReference.getProto(),
+ newMethodReference.getHolderType(),
+ tryMethod ->
+ !keptSignatures.contains(tryMethod)
+ && !reservedInterfaceSignatures.containsValue(tryMethod.getSignature())
+ && !remappedVirtualMethods.containsValue(tryMethod.getSignature())
+ && !newMethodSignatures.containsValue(tryMethod.getSignature()));
}
- } else {
- newMethodReference =
- dexItemFactory.createFreshMethodNameWithoutHolder(
- newMethodReference.getName().toSourceString(),
- newMethodReference.getProto(),
- newMethodReference.getHolderType(),
- tryMethod ->
- !reservedInterfaceSignatures.containsValue(tryMethod.getSignature())
- && !remappedVirtualMethods.containsValue(tryMethod.getSignature())
- && !newMethodSignatures.containsValue(tryMethod.getSignature()));
}
- }
- assert !newMethodSignatures.containsValue(newMethodReference.getSignature());
- newMethodSignatures.put(method, newMethodReference.getSignature());
+ assert !newMethodSignatures.containsValue(newMethodReference.getSignature());
+ newMethodSignatures.put(method, newMethodReference.getSignature());
+ }
}
return fixupProgramMethod(clazz, method, newMethodReference);
@@ -378,27 +395,33 @@
DexEncodedMethod method,
DexMethodSignatureBiMap<DexMethodSignature> renamedClassVirtualMethods,
MutableBidirectionalOneToOneMap<DexEncodedMethod, DexMethodSignature> newMethodSignatures) {
- DexMethodSignature newSignature = newMethodSignatures.get(method);
- if (newSignature == null) {
- // Fix all type references in the method prototype.
- newSignature =
- dexItemFactory.createFreshMethodSignatureName(
- method.getName().toSourceString(),
- null,
- fixupProto(method.getProto()),
- trySignature ->
- !reservedInterfaceSignatures.containsValue(trySignature)
- && !newMethodSignatures.containsValue(trySignature)
- && !renamedClassVirtualMethods.containsValue(trySignature));
- newMethodSignatures.put(method, newSignature);
+ DexMethodSignature newSignature;
+ if (keptSignatures.contains(method)) {
+ newSignature = method.getSignature();
+ } else {
+ newSignature = newMethodSignatures.get(method);
+ if (newSignature == null) {
+ // Fix all type references in the method prototype.
+ newSignature =
+ dexItemFactory.createFreshMethodSignatureName(
+ method.getName().toSourceString(),
+ null,
+ fixupProto(method.getProto()),
+ trySignature ->
+ !keptSignatures.contains(trySignature)
+ && !reservedInterfaceSignatures.containsValue(trySignature)
+ && !newMethodSignatures.containsValue(trySignature)
+ && !renamedClassVirtualMethods.containsValue(trySignature));
+ newMethodSignatures.put(method, newSignature);
+ }
}
// If any of the parameter types have been merged, record the signature mapping so that
// subclasses perform the identical rename.
- if (!reservedInterfaceSignatures.containsKey(method)
+ if (!keptSignatures.contains(method)
+ && !reservedInterfaceSignatures.containsKey(method)
&& Iterables.any(
- newSignature.getProto().getParameterBaseTypes(dexItemFactory),
- mergedClasses::isMergeTarget)) {
+ newSignature.getProto().getBaseTypes(dexItemFactory), mergedClasses::isMergeTarget)) {
renamedClassVirtualMethods.put(method.getSignature(), newSignature);
}
diff --git a/src/main/java/com/android/tools/r8/classmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/classmerging/MergeGroup.java
new file mode 100644
index 0000000..a811666
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/classmerging/MergeGroup.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging;
+
+public abstract class MergeGroup {
+
+ public abstract int size();
+}
diff --git a/src/main/java/com/android/tools/r8/classmerging/Policy.java b/src/main/java/com/android/tools/r8/classmerging/Policy.java
new file mode 100644
index 0000000..e7f52f3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/classmerging/Policy.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
+import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
+import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.VerticalClassMergerPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.VerticalClassMergerPolicyWithPreprocessing;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * The super class of all class merging policies. Most classes will either implement {@link
+ * SingleClassPolicy} or {@link MultiClassPolicy}.
+ */
+public abstract class Policy {
+
+ /** Counter keeping track of how many classes this policy has removed. For debugging only. */
+ public int numberOfRemovedClasses;
+
+ public int numberOfRemovedInterfaces;
+
+ public void clear() {}
+
+ public abstract String getName();
+
+ public boolean isIdentityForInterfaceGroups() {
+ return false;
+ }
+
+ public boolean isSingleClassPolicy() {
+ return false;
+ }
+
+ public SingleClassPolicy asSingleClassPolicy() {
+ return null;
+ }
+
+ public boolean isMultiClassPolicy() {
+ return false;
+ }
+
+ public MultiClassPolicy asMultiClassPolicy() {
+ return null;
+ }
+
+ public boolean isMultiClassPolicyWithPreprocessing() {
+ return false;
+ }
+
+ public MultiClassPolicyWithPreprocessing<?> asMultiClassPolicyWithPreprocessing() {
+ return null;
+ }
+
+ public boolean isVerticalClassMergerPolicy() {
+ return false;
+ }
+
+ public VerticalClassMergerPolicy asVerticalClassMergerPolicy() {
+ return null;
+ }
+
+ public boolean isVerticalClassMergerPolicyWithPreprocessing() {
+ return false;
+ }
+
+ public VerticalClassMergerPolicyWithPreprocessing<?>
+ asVerticalClassMergerPolicyWithPreprocessing() {
+ return null;
+ }
+
+ public boolean shouldSkipPolicy() {
+ return false;
+ }
+
+ /**
+ * Remove all groups containing no or only a single class, as there is no point in merging these.
+ */
+ protected Collection<HorizontalMergeGroup> removeTrivialGroups(
+ Collection<HorizontalMergeGroup> groups) {
+ assert !(groups instanceof ArrayList);
+ groups.removeIf(HorizontalMergeGroup::isTrivial);
+ return groups;
+ }
+
+ public boolean recordRemovedClassesForDebugging(
+ boolean isInterfaceGroup, int previousGroupSize, Collection<HorizontalMergeGroup> newGroups) {
+ assert previousGroupSize >= 2;
+ int previousNumberOfRemovedClasses = previousGroupSize - 1;
+ int newNumberOfRemovedClasses = 0;
+ for (HorizontalMergeGroup newGroup : newGroups) {
+ if (newGroup.isNonTrivial()) {
+ newNumberOfRemovedClasses += newGroup.size() - 1;
+ }
+ }
+ assert previousNumberOfRemovedClasses >= newNumberOfRemovedClasses;
+ int change = previousNumberOfRemovedClasses - newNumberOfRemovedClasses;
+ if (isInterfaceGroup) {
+ numberOfRemovedInterfaces += change;
+ } else {
+ numberOfRemovedClasses += change;
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/classmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/classmerging/PolicyExecutor.java
new file mode 100644
index 0000000..595ff58
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/classmerging/PolicyExecutor.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import com.android.tools.r8.utils.Timing;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * This is a simple policy executor that ensures regular sequential execution of policies. It should
+ * primarily be readable and correct. The SimplePolicyExecutor should be a reference implementation,
+ * against which more efficient policy executors can be compared.
+ */
+public abstract class PolicyExecutor<MG extends MergeGroup> {
+
+ /**
+ * Given an initial collection of class groups which can potentially be merged, run all of the
+ * policies registered to this policy executor on the class groups yielding a new collection of
+ * class groups.
+ */
+ // TODO(b/270398965): Replace LinkedList.
+ @SuppressWarnings("JdkObsolete")
+ public Collection<MG> run(
+ Collection<MG> inputGroups,
+ Collection<? extends Policy> policies,
+ ExecutorService executorService,
+ Timing timing)
+ throws ExecutionException {
+ LinkedList<MG> linkedGroups;
+
+ if (inputGroups instanceof LinkedList) {
+ linkedGroups = (LinkedList<MG>) inputGroups;
+ } else {
+ linkedGroups = new LinkedList<>(inputGroups);
+ }
+
+ for (Policy policy : policies) {
+ if (policy.shouldSkipPolicy()) {
+ continue;
+ }
+
+ timing.begin(policy.getName());
+ linkedGroups = apply(policy, linkedGroups, executorService);
+ timing.end();
+
+ policy.clear();
+
+ if (linkedGroups.isEmpty()) {
+ break;
+ }
+
+ // Any policy should not return any trivial groups.
+ assert linkedGroups.stream().allMatch(group -> group.size() >= 2);
+ }
+
+ return linkedGroups;
+ }
+
+ protected abstract LinkedList<MG> apply(
+ Policy policy, LinkedList<MG> linkedGroups, ExecutorService executorService)
+ throws ExecutionException;
+}
diff --git a/src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java
index a8dcd49..54cdd43 100644
--- a/src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/classmerging/SyntheticArgumentClass.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
import com.google.common.base.Suppliers;
@@ -60,7 +60,7 @@
.createFixedClass(syntheticKindSelector, context, appView, builder -> {});
}
- public SyntheticArgumentClass build(Collection<MergeGroup> mergeGroups) {
+ public SyntheticArgumentClass build(Collection<HorizontalMergeGroup> mergeGroups) {
return build(getDeterministicContext(mergeGroups));
}
@@ -87,9 +87,10 @@
return new SyntheticArgumentClass(syntheticArgumentTypes);
}
- private static DexProgramClass getDeterministicContext(Collection<MergeGroup> mergeGroups) {
+ private static DexProgramClass getDeterministicContext(
+ Collection<HorizontalMergeGroup> mergeGroups) {
// Relies on the determinism of the merge groups.
- MergeGroup mergeGroup = mergeGroups.iterator().next();
+ HorizontalMergeGroup mergeGroup = mergeGroups.iterator().next();
assert mergeGroup.hasTarget();
return mergeGroup.getTarget();
}
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 532abbd..a9ebda5 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -1107,7 +1107,7 @@
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
MethodAccessInfoCollection methodAccessInfoCollection =
appViewWithLiveness.appInfo().getMethodAccessInfoCollection();
- methodAccessInfoCollection.modifier().commit(appViewWithLiveness);
+ assert methodAccessInfoCollection.verify(appViewWithLiveness);
}
return true;
}
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 14c4975..ea46ea8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -298,6 +298,10 @@
return Iterables.filter(virtualMethods(), predicate::test);
}
+ public Iterable<DexClassAndMethod> virtualClassMethods() {
+ return Iterables.transform(virtualMethods(), method -> DexClassAndMethod.create(this, method));
+ }
+
public void addVirtualMethod(DexEncodedMethod method) {
methodCollection.addVirtualMethod(method);
}
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 39c4168..1c568cd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -675,6 +675,7 @@
public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
public final ClassMethods classMethods = new ClassMethods();
public final ConstructorMethods constructorMethods = new ConstructorMethods();
+ public final MethodMethods methodMethods = new MethodMethods();
public final EnumMembers enumMembers = new EnumMembers();
public final AndroidUtilLogMembers androidUtilLogMembers = new AndroidUtilLogMembers();
public final JavaLangReflectArrayMembers javaLangReflectArrayMembers =
@@ -1946,6 +1947,15 @@
}
}
+ public class MethodMethods {
+
+ public final DexMethod invoke =
+ createMethod(
+ methodType, createProto(objectType, objectType, objectArrayType), invokeMethodName);
+
+ private MethodMethods() {}
+ }
+
public class AndroidUtilLogMembers {
public final DexMethod i =
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 95be1fa..4ec188f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -30,6 +30,10 @@
return identical(this, other);
}
+ public final boolean isNotIdenticalTo(DexString other) {
+ return !isIdenticalTo(other);
+ }
+
public static final DexString[] EMPTY_ARRAY = {};
private static final int ARRAY_CHARACTER = '[';
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
index 8b9a38b..f7be3cf 100644
--- a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
@@ -126,4 +126,8 @@
public List<DexProgramClass> getSubclasses(DexProgramClass clazz) {
return immediateSubtypes.getOrDefault(clazz, Collections.emptyList());
}
+
+ public boolean hasSubclasses(DexProgramClass clazz) {
+ return !getSubclasses(clazz).isEmpty();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
index 542a558..3b8681b 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessInfoCollection.java
@@ -212,6 +212,34 @@
});
}
+ public boolean verify(AppView<AppInfoWithLiveness> appView) {
+ assert verifyNoNonResolving(appView);
+ return true;
+ }
+
+ public boolean verifyNoNonResolving(AppView<AppInfoWithLiveness> appView) {
+ verifyNoNonResolving(appView, directInvokes);
+ verifyNoNonResolving(appView, interfaceInvokes);
+ verifyNoNonResolving(appView, staticInvokes);
+ verifyNoNonResolving(appView, superInvokes);
+ verifyNoNonResolving(appView, virtualInvokes);
+ return true;
+ }
+
+ private void verifyNoNonResolving(
+ AppView<AppInfoWithLiveness> appView, Map<DexMethod, ?> invokes) {
+ if (!isThrowingMap(invokes)) {
+ for (DexMethod method : invokes.keySet()) {
+ MethodResolutionResult result =
+ appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method);
+ assert !result.isFailedResolution()
+ : "Unexpected method that does not resolve: " + method.toSourceString();
+ assert !result.isSignaturePolymorphicResolution(method, appView.dexItemFactory())
+ : "Unexpected signature polymorphic resolution: " + method.toSourceString();
+ }
+ }
+ }
+
public abstract static class Builder<T extends Map<DexMethod, ProgramMethodSet>> {
private final T directInvokes;
@@ -374,29 +402,6 @@
});
}
- public boolean verifyNoNonResolving(AppView<AppInfoWithLiveness> appView) {
- verifyNoNonResolving(appView, directInvokes);
- verifyNoNonResolving(appView, interfaceInvokes);
- verifyNoNonResolving(appView, staticInvokes);
- verifyNoNonResolving(appView, superInvokes);
- verifyNoNonResolving(appView, virtualInvokes);
- return true;
- }
-
- private void verifyNoNonResolving(
- AppView<AppInfoWithLiveness> appView, Map<DexMethod, ?> invokes) {
- if (!isThrowingMap(invokes)) {
- for (DexMethod method : invokes.keySet()) {
- MethodResolutionResult result =
- appView.appInfo().unsafeResolveMethodDueToDexFormatLegacy(method);
- assert !result.isFailedResolution()
- : "Unexpected method that does not resolve: " + method.toSourceString();
- assert !result.isSignaturePolymorphicResolution(method, appView.dexItemFactory())
- : "Unexpected signature polymorphic resolution: " + method.toSourceString();
- }
- }
- }
-
public MethodAccessInfoCollection build() {
return new MethodAccessInfoCollection(
directInvokes, interfaceInvokes, staticInvokes, superInvokes, virtualInvokes);
@@ -437,9 +442,5 @@
collection.forEachSuperInvoke(this::registerInvokeSuperInContexts);
collection.forEachVirtualInvoke(this::registerInvokeVirtualInContexts);
}
-
- public void commit(AppView<AppInfoWithLiveness> appView) {
- assert verifyNoNonResolving(appView);
- }
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
index a897bd0..77fef31 100644
--- a/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
+++ b/src/main/java/com/android/tools/r8/graph/RewrittenPrototypeDescriptionMethodOptimizationInfoFixer.java
@@ -7,8 +7,11 @@
import com.android.tools.r8.graph.proto.ArgumentInfo;
import com.android.tools.r8.graph.proto.ArgumentInfoCollection;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
+import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintFactory;
+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.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
@@ -157,6 +160,36 @@
return fixupArgumentInfo(arguments);
}
+ @Override
+ public DynamicType fixupDynamicType(DynamicType dynamicType) {
+ if (!prototypeChanges.hasRewrittenReturnInfo()) {
+ return dynamicType;
+ }
+ RewrittenTypeInfo rewrittenReturnInfo = prototypeChanges.getRewrittenReturnInfo();
+ if (rewrittenReturnInfo.getNewType().isPrimitiveType()
+ || rewrittenReturnInfo.getNewType().isVoidType()) {
+ return DynamicType.unknown();
+ }
+ return dynamicType;
+ }
+
+ @Override
+ public AbstractValue fixupAbstractReturnValue(
+ AppView<AppInfoWithLiveness> appView, AbstractValue returnValue) {
+ if (!prototypeChanges.hasRewrittenReturnInfo()) {
+ return returnValue;
+ }
+ RewrittenTypeInfo rewrittenReturnInfo = prototypeChanges.getRewrittenReturnInfo();
+ if (rewrittenReturnInfo.getNewType().isPrimitiveType()) {
+ // This covers for number unboxing, however, enum unboxing should never have a single
+ // boxed primitive as return value.
+ if (returnValue.isSingleBoxedPrimitive()) {
+ return returnValue.asSingleBoxedPrimitive().toPrimitive(appView.abstractValueFactory());
+ }
+ }
+ return returnValue;
+ }
+
private BitSet fixupArgumentInfo(BitSet bitSet) {
if (getArgumentInfoCollection().isEmpty() || bitSet == null) {
return bitSet;
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLensUtils.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLensUtils.java
index b7175d3..5a7c463 100644
--- a/src/main/java/com/android/tools/r8/graph/lens/GraphLensUtils.java
+++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLensUtils.java
@@ -10,16 +10,10 @@
public static Deque<NonIdentityGraphLens> extractNonIdentityLenses(GraphLens lens) {
Deque<NonIdentityGraphLens> lenses = new ArrayDeque<>();
- if (lens.isNonIdentityLens()) {
- lenses.addFirst(lens.asNonIdentityLens());
- while (true) {
- GraphLens previous = lenses.getFirst().getPrevious();
- if (previous.isNonIdentityLens()) {
- lenses.addFirst(previous.asNonIdentityLens());
- } else {
- break;
- }
- }
+ while (lens.isNonIdentityLens()) {
+ NonIdentityGraphLens nonIdentityLens = lens.asNonIdentityLens();
+ lenses.addFirst(nonIdentityLens);
+ lens = nonIdentityLens.getPrevious();
}
return lenses;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
index 8edf836..778132e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassInstanceFieldsMerger.java
@@ -33,7 +33,9 @@
DexEncodedField[] merge();
static ClassInstanceFieldsMerger create(
- AppView<?> appView, HorizontalClassMergerGraphLens.Builder lensBuilder, MergeGroup group) {
+ AppView<?> appView,
+ HorizontalClassMergerGraphLens.Builder lensBuilder,
+ HorizontalMergeGroup group) {
if (appView.hasClassHierarchy()) {
return new ClassInstanceFieldsMergerImpl(appView.withClassHierarchy(), lensBuilder, group);
} else {
@@ -137,7 +139,7 @@
class ClassInstanceFieldsMergerImpl implements ClassInstanceFieldsMerger {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
- private final MergeGroup group;
+ private final HorizontalMergeGroup group;
@SuppressWarnings("BadImport")
private final Builder lensBuilder;
@@ -149,7 +151,7 @@
private ClassInstanceFieldsMergerImpl(
AppView<? extends AppInfoWithClassHierarchy> appView,
HorizontalClassMergerGraphLens.Builder lensBuilder,
- MergeGroup group) {
+ HorizontalMergeGroup group) {
this.appView = appView;
this.group = group;
this.lensBuilder = lensBuilder;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index ecbf167..2097c3a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -60,7 +60,7 @@
private final AppView<?> appView;
private final Mode mode;
- private final MergeGroup group;
+ private final HorizontalMergeGroup group;
private final DexItemFactory dexItemFactory;
private final HorizontalClassMergerGraphLens.Builder lensBuilder;
@@ -81,7 +81,7 @@
IRCodeProvider codeProvider,
Mode mode,
HorizontalClassMergerGraphLens.Builder lensBuilder,
- MergeGroup group,
+ HorizontalMergeGroup group,
Collection<VirtualMethodMerger> virtualMethodMergers) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
@@ -376,9 +376,10 @@
private final AppView<?> appView;
private final IRCodeProvider codeProvider;
private final Mode mode;
- private final MergeGroup group;
+ private final HorizontalMergeGroup group;
- public Builder(AppView<?> appView, IRCodeProvider codeProvider, MergeGroup group, Mode mode) {
+ public Builder(
+ AppView<?> appView, IRCodeProvider codeProvider, HorizontalMergeGroup group, Mode mode) {
this.appView = appView;
this.codeProvider = codeProvider;
this.group = group;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
index 81ec45e..dd6ddf2 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassStaticFieldsMerger.java
@@ -17,7 +17,7 @@
private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
- private final MergeGroup group;
+ private final HorizontalMergeGroup group;
@SuppressWarnings("BadImport")
private final Builder lensBuilder;
@@ -25,7 +25,9 @@
private final Map<DexField, DexEncodedField> targetFields = new LinkedHashMap<>();
public ClassStaticFieldsMerger(
- AppView<?> appView, HorizontalClassMergerGraphLens.Builder lensBuilder, MergeGroup group) {
+ AppView<?> appView,
+ HorizontalClassMergerGraphLens.Builder lensBuilder,
+ HorizontalMergeGroup group) {
this.appView = appView;
this.dexItemFactory = appView.dexItemFactory();
this.group = group;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 57ff66a..d7c99ba 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
+import com.android.tools.r8.classmerging.Policy;
import com.android.tools.r8.classmerging.SyntheticArgumentClass;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -126,8 +127,9 @@
// Run the policies on all program classes to produce a final grouping.
List<Policy> policies =
PolicyScheduler.getPolicies(appView, codeProvider, mode, runtimeTypeCheckInfo);
- Collection<MergeGroup> groups =
- new PolicyExecutor().run(getInitialGroups(), policies, executorService, timing);
+ Collection<HorizontalMergeGroup> groups =
+ new HorizontalClassMergerPolicyExecutor()
+ .run(getInitialGroups(), policies, executorService, timing);
// If there are no groups, then end horizontal class merging.
if (groups.isEmpty()) {
@@ -186,7 +188,7 @@
if (mode.isInitial()) {
fieldAccessInfoCollectionModifier = createFieldAccessInfoCollectionModifier(groups);
} else {
- assert groups.stream().noneMatch(MergeGroup::hasClassIdField);
+ assert groups.stream().noneMatch(HorizontalMergeGroup::hasClassIdField);
}
// Set the new graph lens before finalizing any synthetic code.
@@ -271,11 +273,11 @@
}
private FieldAccessInfoCollectionModifier createFieldAccessInfoCollectionModifier(
- Collection<MergeGroup> groups) {
+ Collection<HorizontalMergeGroup> groups) {
assert mode.isInitial();
FieldAccessInfoCollectionModifier.Builder builder =
new FieldAccessInfoCollectionModifier.Builder();
- for (MergeGroup group : groups) {
+ for (HorizontalMergeGroup group : groups) {
if (group.hasClassIdField()) {
DexProgramClass target = group.getTarget();
target.forEachProgramInstanceInitializerMatching(
@@ -291,7 +293,7 @@
}
private void transformIncompleteCode(
- Collection<MergeGroup> groups,
+ Collection<HorizontalMergeGroup> groups,
HorizontalClassMergerGraphLens horizontalClassMergerGraphLens,
ExecutorService executorService)
throws ExecutionException {
@@ -321,7 +323,8 @@
}
private boolean verifyNoIncompleteCode(
- Collection<MergeGroup> groups, ExecutorService executorService) throws ExecutionException {
+ Collection<HorizontalMergeGroup> groups, ExecutorService executorService)
+ throws ExecutionException {
ThreadUtils.processItems(
groups,
group -> {
@@ -356,9 +359,9 @@
// TODO(b/270398965): Replace LinkedList.
@SuppressWarnings("JdkObsolete")
- private List<MergeGroup> getInitialGroups() {
- MergeGroup initialClassGroup = new MergeGroup();
- MergeGroup initialInterfaceGroup = new MergeGroup();
+ private List<HorizontalMergeGroup> getInitialGroups() {
+ HorizontalMergeGroup initialClassGroup = new HorizontalMergeGroup();
+ HorizontalMergeGroup initialInterfaceGroup = new HorizontalMergeGroup();
for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
if (clazz.isInterface()) {
initialInterfaceGroup.add(clazz);
@@ -366,10 +369,10 @@
initialClassGroup.add(clazz);
}
}
- List<MergeGroup> initialGroups = new LinkedList<>();
+ List<HorizontalMergeGroup> initialGroups = new LinkedList<>();
initialGroups.add(initialClassGroup);
initialGroups.add(initialInterfaceGroup);
- initialGroups.removeIf(MergeGroup::isTrivial);
+ initialGroups.removeIf(HorizontalMergeGroup::isTrivial);
return initialGroups;
}
@@ -380,9 +383,9 @@
private List<ClassMerger> initializeClassMergers(
IRCodeProvider codeProvider,
HorizontalClassMergerGraphLens.Builder lensBuilder,
- Collection<MergeGroup> groups) {
+ Collection<HorizontalMergeGroup> groups) {
List<ClassMerger> classMergers = new ArrayList<>(groups.size());
- for (MergeGroup group : groups) {
+ for (HorizontalMergeGroup group : groups) {
assert group.isNonTrivial();
assert group.hasInstanceFieldMap();
assert group.hasTarget();
@@ -438,8 +441,8 @@
@SuppressWarnings("ReferenceEquality")
private static boolean verifyNoCyclesInInterfaceHierarchies(
- AppView<?> appView, Collection<MergeGroup> groups) {
- for (MergeGroup group : groups) {
+ AppView<?> appView, Collection<HorizontalMergeGroup> groups) {
+ for (HorizontalMergeGroup group : groups) {
if (group.isClassGroup()) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerPolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerPolicyExecutor.java
new file mode 100644
index 0000000..be9f787
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerPolicyExecutor.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.horizontalclassmerging;
+
+import com.android.tools.r8.classmerging.Policy;
+import com.android.tools.r8.classmerging.PolicyExecutor;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class HorizontalClassMergerPolicyExecutor extends PolicyExecutor<HorizontalMergeGroup> {
+
+ @Override
+ protected LinkedList<HorizontalMergeGroup> apply(
+ Policy policy, LinkedList<HorizontalMergeGroup> linkedGroups, ExecutorService executorService)
+ throws ExecutionException {
+ if (policy.isSingleClassPolicy()) {
+ applySingleClassPolicy(policy.asSingleClassPolicy(), linkedGroups);
+ } else {
+ if (policy.isMultiClassPolicy()) {
+ linkedGroups = applyMultiClassPolicy(policy.asMultiClassPolicy(), linkedGroups);
+ } else {
+ assert policy.isMultiClassPolicyWithPreprocessing();
+ linkedGroups =
+ applyMultiClassPolicyWithPreprocessing(
+ policy.asMultiClassPolicyWithPreprocessing(), linkedGroups, executorService);
+ }
+ }
+ return linkedGroups;
+ }
+
+ void applySingleClassPolicy(SingleClassPolicy policy, LinkedList<HorizontalMergeGroup> groups) {
+ Iterator<HorizontalMergeGroup> i = groups.iterator();
+ while (i.hasNext()) {
+ HorizontalMergeGroup group = i.next();
+ boolean isInterfaceGroup = group.isInterfaceGroup();
+ int previousGroupSize = group.size();
+ group.removeIf(clazz -> !policy.canMerge(clazz));
+ assert policy.recordRemovedClassesForDebugging(
+ isInterfaceGroup, previousGroupSize, ImmutableList.of(group));
+ if (group.isTrivial()) {
+ i.remove();
+ }
+ }
+ }
+
+ // TODO(b/270398965): Replace LinkedList.
+ @SuppressWarnings("JdkObsolete")
+ private LinkedList<HorizontalMergeGroup> applyMultiClassPolicy(
+ MultiClassPolicy policy, LinkedList<HorizontalMergeGroup> groups) {
+ // For each group apply the multi class policy and add all the new groups together.
+ LinkedList<HorizontalMergeGroup> newGroups = new LinkedList<>();
+ groups.forEach(
+ group -> {
+ boolean isInterfaceGroup = group.isInterfaceGroup();
+ int previousGroupSize = group.size();
+ Collection<HorizontalMergeGroup> policyGroups = policy.apply(group);
+ policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
+ assert policy.recordRemovedClassesForDebugging(
+ isInterfaceGroup, previousGroupSize, policyGroups);
+ newGroups.addAll(policyGroups);
+ });
+ return newGroups;
+ }
+
+ // TODO(b/270398965): Replace LinkedList.
+ @SuppressWarnings("JdkObsolete")
+ private <T> LinkedList<HorizontalMergeGroup> applyMultiClassPolicyWithPreprocessing(
+ MultiClassPolicyWithPreprocessing<T> policy,
+ LinkedList<HorizontalMergeGroup> groups,
+ ExecutorService executorService)
+ throws ExecutionException {
+ // For each group apply the multi class policy and add all the new groups together.
+ T data = policy.preprocess(groups, executorService);
+ LinkedList<HorizontalMergeGroup> newGroups = new LinkedList<>();
+ groups.forEach(
+ group -> {
+ boolean isInterfaceGroup = group.isInterfaceGroup();
+ int previousGroupSize = group.size();
+ Collection<HorizontalMergeGroup> policyGroups = policy.apply(group, data);
+ policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
+ assert policy.recordRemovedClassesForDebugging(
+ isInterfaceGroup, previousGroupSize, policyGroups);
+ newGroups.addAll(policyGroups);
+ });
+ return newGroups;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalMergeGroup.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalMergeGroup.java
index 8062a16..9210ef9 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalMergeGroup.java
@@ -6,6 +6,7 @@
package com.android.tools.r8.horizontalclassmerging;
+import com.android.tools.r8.classmerging.MergeGroup;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
@@ -26,7 +27,7 @@
import java.util.function.Consumer;
import java.util.function.Predicate;
-public class MergeGroup implements Collection<DexProgramClass> {
+public class HorizontalMergeGroup extends MergeGroup implements Collection<DexProgramClass> {
public static class Metadata {}
@@ -42,21 +43,21 @@
// TODO(b/270398965): Replace LinkedList.
@SuppressWarnings("JdkObsolete")
- public MergeGroup() {
+ public HorizontalMergeGroup() {
this.classes = new LinkedList<>();
}
- public MergeGroup(DexProgramClass clazz) {
+ public HorizontalMergeGroup(DexProgramClass clazz) {
this();
add(clazz);
}
- public MergeGroup(Iterable<DexProgramClass> classes) {
+ public HorizontalMergeGroup(Iterable<DexProgramClass> classes) {
this();
Iterables.addAll(this.classes, classes);
}
- public void applyMetadataFrom(MergeGroup group) {
+ public void applyMetadataFrom(HorizontalMergeGroup group) {
if (metadata == null) {
metadata = group.metadata;
}
@@ -67,7 +68,7 @@
return classes.add(clazz);
}
- public boolean add(MergeGroup group) {
+ public boolean add(HorizontalMergeGroup group) {
return classes.addAll(group.getClasses());
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
index f832f3a..197f2b8 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontallyMergedClasses.java
@@ -112,11 +112,11 @@
mergedClasses.put(source, target);
}
- void addMergeGroup(MergeGroup group) {
+ void addMergeGroup(HorizontalMergeGroup group) {
group.forEachSource(clazz -> add(clazz.getType(), group.getTarget().getType()));
}
- Builder addMergeGroups(Iterable<MergeGroup> groups) {
+ Builder addMergeGroups(Iterable<HorizontalMergeGroup> groups) {
groups.forEach(this::addMergeGroup);
return this;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
index 67022ed..f49af82 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerAnalysis.java
@@ -40,7 +40,7 @@
public static InstanceInitializerDescription analyze(
AppView<? extends AppInfoWithClassHierarchy> appView,
IRCodeProvider codeProvider,
- MergeGroup group,
+ HorizontalMergeGroup group,
InstanceInitializer instanceInitializer) {
if (instanceInitializer.isAbsent()) {
InstanceInitializerDescription.Builder builder =
@@ -69,7 +69,7 @@
public static InstanceInitializerDescription analyze(
AppView<? extends AppInfoWithClassHierarchy> appView,
IRCodeProvider codeProvider,
- MergeGroup group,
+ HorizontalMergeGroup group,
ProgramMethod instanceInitializer) {
InstanceInitializerDescription.Builder builder =
InstanceInitializerDescription.builder(appView, instanceInitializer);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
index 2360c36..dc5f15e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerDescription.java
@@ -74,7 +74,7 @@
*/
public IncompleteMergedInstanceInitializerCode createCfCode(
DexMethod originalMethodReference,
- MergeGroup group,
+ HorizontalMergeGroup group,
boolean hasClassId,
int extraNulls) {
return new IncompleteMergedInstanceInitializerCode(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
index 54f4898..b522877 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMerger.java
@@ -45,7 +45,7 @@
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final Reference2IntMap<DexType> classIdentifiers;
private final DexItemFactory dexItemFactory;
- private final MergeGroup group;
+ private final HorizontalMergeGroup group;
private final List<ProgramMethod> instanceInitializers;
private final InstanceInitializerDescription instanceInitializerDescription;
private final HorizontalClassMergerGraphLens.Builder lensBuilder;
@@ -54,7 +54,7 @@
InstanceInitializerMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
Reference2IntMap<DexType> classIdentifiers,
- MergeGroup group,
+ HorizontalMergeGroup group,
List<ProgramMethod> instanceInitializers,
HorizontalClassMergerGraphLens.Builder lensBuilder,
Mode mode) {
@@ -64,7 +64,7 @@
InstanceInitializerMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
Reference2IntMap<DexType> classIdentifiers,
- MergeGroup group,
+ HorizontalMergeGroup group,
List<ProgramMethod> instanceInitializers,
HorizontalClassMergerGraphLens.Builder lensBuilder,
Mode mode,
@@ -214,7 +214,7 @@
return this;
}
- public List<InstanceInitializerMerger> build(MergeGroup group) {
+ public List<InstanceInitializerMerger> build(HorizontalMergeGroup group) {
assert instanceInitializerGroups.stream().noneMatch(List::isEmpty);
return ListUtils.map(
instanceInitializerGroups,
@@ -224,7 +224,7 @@
}
public InstanceInitializerMerger buildSingle(
- MergeGroup group, InstanceInitializerDescription instanceInitializerDescription) {
+ HorizontalMergeGroup group, InstanceInitializerDescription instanceInitializerDescription) {
assert instanceInitializerGroups.stream().noneMatch(List::isEmpty);
assert instanceInitializerGroups.size() == 1;
List<ProgramMethod> instanceInitializers = ListUtils.first(instanceInitializerGroups);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
index 50a0f23..62d7522 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/InstanceInitializerMergerCollection.java
@@ -42,7 +42,7 @@
AppView<?> appView,
Reference2IntMap<DexType> classIdentifiers,
IRCodeProvider codeProvider,
- MergeGroup group,
+ HorizontalMergeGroup group,
HorizontalClassMergerGraphLens.Builder lensBuilder,
Mode mode) {
if (!appView.hasClassHierarchy()) {
@@ -130,7 +130,7 @@
instanceInitializerMergers, equivalentInstanceInitializerMergers);
}
- private static boolean verifyNoInstanceInitializers(MergeGroup group) {
+ private static boolean verifyNoInstanceInitializers(HorizontalMergeGroup group) {
group.forEach(
clazz -> {
assert !clazz.programInstanceInitializers().iterator().hasNext();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
index 63420fe..8bad6ec 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.horizontalclassmerging;
+import com.android.tools.r8.classmerging.Policy;
import java.util.Collection;
public abstract class MultiClassPolicy extends Policy {
@@ -16,7 +17,7 @@
* merged. If the policy detects no issues then `group` will be returned unchanged. If classes
* cannot be merged with any other classes they are returned as singleton lists.
*/
- public abstract Collection<MergeGroup> apply(MergeGroup group);
+ public abstract Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group);
@Override
public boolean isMultiClassPolicy() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
index d634479..416164b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.horizontalclassmerging;
+import com.android.tools.r8.classmerging.Policy;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -19,9 +20,10 @@
* merged. If the policy detects no issues then `group` will be returned unchanged. If classes
* cannot be merged with any other classes they are returned as singleton lists.
*/
- public abstract Collection<MergeGroup> apply(MergeGroup group, T data);
+ public abstract Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group, T data);
- public abstract T preprocess(Collection<MergeGroup> groups, ExecutorService executorService)
+ public abstract T preprocess(
+ Collection<HorizontalMergeGroup> groups, ExecutorService executorService)
throws ExecutionException;
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
index aa3ca62..40399c0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassSameReferencePolicy.java
@@ -12,12 +12,12 @@
public abstract class MultiClassSameReferencePolicy<T> extends MultiClassPolicy {
@Override
- public final Collection<MergeGroup> apply(MergeGroup group) {
- Map<T, MergeGroup> groups = new LinkedHashMap<>();
+ public final Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
+ Map<T, HorizontalMergeGroup> groups = new LinkedHashMap<>();
for (DexProgramClass clazz : group) {
T mergeKey = getMergeKey(clazz);
if (mergeKey != null) {
- groups.computeIfAbsent(mergeKey, ignore -> new MergeGroup()).add(clazz);
+ groups.computeIfAbsent(mergeKey, ignore -> new HorizontalMergeGroup()).add(clazz);
}
}
removeTrivialGroups(groups.values());
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
deleted file mode 100644
index 5bff09e..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging;
-
-import java.util.ArrayList;
-import java.util.Collection;
-
-/**
- * The super class of all horizontal class merging policies. Most classes will either implement
- * {@link SingleClassPolicy} or {@link MultiClassPolicy}.
- */
-public abstract class Policy {
-
- /** Counter keeping track of how many classes this policy has removed. For debugging only. */
- public int numberOfRemovedClasses;
-
- public int numberOfRemovedInterfaces;
-
- public void clear() {}
-
- public abstract String getName();
-
- public boolean isIdentityForInterfaceGroups() {
- return false;
- }
-
- public boolean isSingleClassPolicy() {
- return false;
- }
-
- public SingleClassPolicy asSingleClassPolicy() {
- return null;
- }
-
- public boolean isMultiClassPolicy() {
- return false;
- }
-
- public MultiClassPolicy asMultiClassPolicy() {
- return null;
- }
-
- public boolean isMultiClassPolicyWithPreprocessing() {
- return false;
- }
-
- public MultiClassPolicyWithPreprocessing<?> asMultiClassPolicyWithPreprocessing() {
- return null;
- }
-
- public boolean shouldSkipPolicy() {
- return false;
- }
-
- /**
- * Remove all groups containing no or only a single class, as there is no point in merging these.
- */
- protected Collection<MergeGroup> removeTrivialGroups(Collection<MergeGroup> groups) {
- assert !(groups instanceof ArrayList);
- groups.removeIf(MergeGroup::isTrivial);
- return groups;
- }
-
- boolean recordRemovedClassesForDebugging(
- boolean isInterfaceGroup, int previousGroupSize, Collection<MergeGroup> newGroups) {
- assert previousGroupSize >= 2;
- int previousNumberOfRemovedClasses = previousGroupSize - 1;
- int newNumberOfRemovedClasses = 0;
- for (MergeGroup newGroup : newGroups) {
- if (newGroup.isNonTrivial()) {
- newNumberOfRemovedClasses += newGroup.size() - 1;
- }
- }
- assert previousNumberOfRemovedClasses >= newNumberOfRemovedClasses;
- int change = previousNumberOfRemovedClasses - newNumberOfRemovedClasses;
- if (isInterfaceGroup) {
- numberOfRemovedInterfaces += change;
- } else {
- numberOfRemovedClasses += change;
- }
- return true;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
deleted file mode 100644
index 28ec89d..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.horizontalclassmerging;
-
-import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-
-/**
- * This is a simple policy executor that ensures regular sequential execution of policies. It should
- * primarily be readable and correct. The SimplePolicyExecutor should be a reference implementation,
- * against which more efficient policy executors can be compared.
- */
-public class PolicyExecutor {
-
- private void applySingleClassPolicy(SingleClassPolicy policy, LinkedList<MergeGroup> groups) {
- Iterator<MergeGroup> i = groups.iterator();
- while (i.hasNext()) {
- MergeGroup group = i.next();
- boolean isInterfaceGroup = group.isInterfaceGroup();
- int previousGroupSize = group.size();
- group.removeIf(clazz -> !policy.canMerge(clazz));
- assert policy.recordRemovedClassesForDebugging(
- isInterfaceGroup, previousGroupSize, ImmutableList.of(group));
- if (group.isTrivial()) {
- i.remove();
- }
- }
- }
-
- // TODO(b/270398965): Replace LinkedList.
- @SuppressWarnings("JdkObsolete")
- private LinkedList<MergeGroup> applyMultiClassPolicy(
- MultiClassPolicy policy, LinkedList<MergeGroup> groups) {
- // For each group apply the multi class policy and add all the new groups together.
- LinkedList<MergeGroup> newGroups = new LinkedList<>();
- groups.forEach(
- group -> {
- boolean isInterfaceGroup = group.isInterfaceGroup();
- int previousGroupSize = group.size();
- Collection<MergeGroup> policyGroups = policy.apply(group);
- policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
- assert policy.recordRemovedClassesForDebugging(
- isInterfaceGroup, previousGroupSize, policyGroups);
- newGroups.addAll(policyGroups);
- });
- return newGroups;
- }
-
- // TODO(b/270398965): Replace LinkedList.
- @SuppressWarnings("JdkObsolete")
- private <T> LinkedList<MergeGroup> applyMultiClassPolicyWithPreprocessing(
- MultiClassPolicyWithPreprocessing<T> policy,
- LinkedList<MergeGroup> groups,
- ExecutorService executorService)
- throws ExecutionException {
- // For each group apply the multi class policy and add all the new groups together.
- T data = policy.preprocess(groups, executorService);
- LinkedList<MergeGroup> newGroups = new LinkedList<>();
- groups.forEach(
- group -> {
- boolean isInterfaceGroup = group.isInterfaceGroup();
- int previousGroupSize = group.size();
- Collection<MergeGroup> policyGroups = policy.apply(group, data);
- policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
- assert policy.recordRemovedClassesForDebugging(
- isInterfaceGroup, previousGroupSize, policyGroups);
- newGroups.addAll(policyGroups);
- });
- return newGroups;
- }
-
- /**
- * Given an initial collection of class groups which can potentially be merged, run all of the
- * policies registered to this policy executor on the class groups yielding a new collection of
- * class groups.
- */
- // TODO(b/270398965): Replace LinkedList.
- @SuppressWarnings("JdkObsolete")
- public Collection<MergeGroup> run(
- Collection<MergeGroup> inputGroups,
- Collection<Policy> policies,
- ExecutorService executorService,
- Timing timing)
- throws ExecutionException {
- LinkedList<MergeGroup> linkedGroups;
-
- if (inputGroups instanceof LinkedList) {
- linkedGroups = (LinkedList<MergeGroup>) inputGroups;
- } else {
- linkedGroups = new LinkedList<>(inputGroups);
- }
-
- for (Policy policy : policies) {
- if (policy.shouldSkipPolicy()) {
- continue;
- }
-
- timing.begin(policy.getName());
- if (policy.isSingleClassPolicy()) {
- applySingleClassPolicy(policy.asSingleClassPolicy(), linkedGroups);
- } else if (policy.isMultiClassPolicy()) {
- linkedGroups = applyMultiClassPolicy(policy.asMultiClassPolicy(), linkedGroups);
- } else {
- assert policy.isMultiClassPolicyWithPreprocessing();
- linkedGroups =
- applyMultiClassPolicyWithPreprocessing(
- policy.asMultiClassPolicyWithPreprocessing(), linkedGroups, executorService);
- }
- timing.end();
-
- policy.clear();
-
- if (linkedGroups.isEmpty()) {
- break;
- }
-
- // Any policy should not return any trivial groups.
- assert linkedGroups.stream().allMatch(group -> group.size() >= 2);
- }
-
- return linkedGroups;
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index da96bcf..fa18dbb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.horizontalclassmerging;
+import com.android.tools.r8.classmerging.Policy;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
index db2d8eb..7c9aedb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.horizontalclassmerging;
+import com.android.tools.r8.classmerging.Policy;
import com.android.tools.r8.graph.DexProgramClass;
public abstract class SingleClassPolicy extends Policy {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
index 5b1ee78..6175c5a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/VirtualMethodMerger.java
@@ -49,13 +49,13 @@
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final DexItemFactory dexItemFactory;
- private final MergeGroup group;
+ private final HorizontalMergeGroup group;
private final List<ProgramMethod> methods;
private final SuperMethodReference superMethod;
public VirtualMethodMerger(
AppView<? extends AppInfoWithClassHierarchy> appView,
- MergeGroup group,
+ HorizontalMergeGroup group,
List<ProgramMethod> methods,
SuperMethodReference superMethod) {
this.appView = appView;
@@ -75,7 +75,7 @@
/** Get the super method handle if this method overrides a parent method. */
private SuperMethodReference superMethod(
- AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
+ AppView<? extends AppInfoWithClassHierarchy> appView, HorizontalMergeGroup group) {
DexMethod template = methods.iterator().next().getReference();
SingleResolutionResult<?> resolutionResult =
appView
@@ -98,7 +98,7 @@
}
public VirtualMethodMerger build(
- AppView<? extends AppInfoWithClassHierarchy> appView, MergeGroup group) {
+ AppView<? extends AppInfoWithClassHierarchy> appView, HorizontalMergeGroup group) {
// If not all the classes are in the merge group, find the fallback super method to call.
SuperMethodReference superMethod =
methods.size() < group.size() ? superMethod(appView, group) : null;
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
index a0b8b9b..19efa91 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/ClassInitializerMerger.java
@@ -24,7 +24,7 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.IRMetadata;
@@ -59,7 +59,7 @@
this.classInitializers = classInitializers;
}
- public static ClassInitializerMerger create(MergeGroup group) {
+ public static ClassInitializerMerger create(HorizontalMergeGroup group) {
ClassInitializerMerger.Builder builder = new ClassInitializerMerger.Builder();
group.forEach(
clazz -> {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassThatMatchesPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassThatMatchesPolicy.java
index 17bb034..82cc1b5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassThatMatchesPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/AtMostOneClassThatMatchesPolicy.java
@@ -7,7 +7,7 @@
import static com.android.tools.r8.utils.IteratorUtils.createCircularIterator;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -20,12 +20,12 @@
// TODO(b/270398965): Replace LinkedList.
@SuppressWarnings("JdkObsolete")
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
// Create a new merge group for each class that we want at most one of.
- List<MergeGroup> newGroups = new LinkedList<>();
+ List<HorizontalMergeGroup> newGroups = new LinkedList<>();
for (DexProgramClass clazz : group) {
if (atMostOneOf(clazz)) {
- newGroups.add(new MergeGroup(clazz));
+ newGroups.add(new HorizontalMergeGroup(clazz));
}
}
@@ -36,7 +36,7 @@
}
// Otherwise, fill up the new merge groups with the remaining classes.
- Iterator<MergeGroup> newGroupsIterator = createCircularIterator(newGroups);
+ Iterator<HorizontalMergeGroup> newGroupsIterator = createCircularIterator(newGroups);
for (DexProgramClass clazz : group) {
if (!atMostOneOf(clazz)) {
newGroupsIterator.next().add(clazz);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
index b9c4c3c..16e17f0 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/FinalizeMergeGroup.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SetUtils;
@@ -34,7 +34,7 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
if (appView.enableWholeProgramOptimizations()) {
if (mode.isInitial() || group.isInterfaceGroup()) {
group.selectTarget(appView);
@@ -63,7 +63,7 @@
return true;
}
- private boolean verifyAlreadyFinalized(MergeGroup group) {
+ private boolean verifyAlreadyFinalized(HorizontalMergeGroup group) {
assert group.hasTarget();
assert group.getClasses().contains(group.getTarget());
assert group.hasInstanceFieldMap();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
index d271464..728cf5e 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitClassGroups.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import java.util.Collection;
import java.util.Collections;
@@ -27,13 +27,13 @@
// TODO(b/270398965): Replace LinkedList.
@Override
@SuppressWarnings({"JdkObsolete", "MixedMutabilityReturnType"})
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
if (group.size() <= maxGroupSize || group.isInterfaceGroup()) {
return Collections.singletonList(group);
}
- LinkedList<MergeGroup> newGroups = new LinkedList<>();
- MergeGroup newGroup = createNewGroup(newGroups);
+ LinkedList<HorizontalMergeGroup> newGroups = new LinkedList<>();
+ HorizontalMergeGroup newGroup = createNewGroup(newGroups);
for (DexProgramClass clazz : group) {
if (newGroup.size() == maxGroupSize) {
newGroup = createNewGroup(newGroups);
@@ -42,7 +42,7 @@
}
if (newGroup.size() == 1) {
if (maxGroupSize == 2) {
- MergeGroup removedGroup = newGroups.removeLast();
+ HorizontalMergeGroup removedGroup = newGroups.removeLast();
assert removedGroup == newGroup;
} else {
newGroup.add(newGroups.getFirst().removeLast());
@@ -51,8 +51,8 @@
return newGroups;
}
- private MergeGroup createNewGroup(LinkedList<MergeGroup> newGroups) {
- MergeGroup newGroup = new MergeGroup();
+ private HorizontalMergeGroup createNewGroup(LinkedList<HorizontalMergeGroup> newGroups) {
+ HorizontalMergeGroup newGroup = new HorizontalMergeGroup();
newGroups.add(newGroup);
return newGroup;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java
index adee1f4..414003a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/LimitInterfaceGroups.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import java.util.Collection;
import java.util.Collections;
@@ -25,24 +25,24 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
if (group.isClassGroup()) {
return Collections.singletonList(group);
}
// Mapping from new merge groups to their size.
- Map<MergeGroup, Integer> newGroups = new LinkedHashMap<>();
+ Map<HorizontalMergeGroup, Integer> newGroups = new LinkedHashMap<>();
for (DexProgramClass clazz : group) {
processClass(clazz, newGroups);
}
return removeTrivialGroups(newGroups.keySet());
}
- private void processClass(DexProgramClass clazz, Map<MergeGroup, Integer> newGroups) {
+ private void processClass(DexProgramClass clazz, Map<HorizontalMergeGroup, Integer> newGroups) {
int increment = clazz.getMethodCollection().size();
// Find an existing group.
- for (Entry<MergeGroup, Integer> entry : newGroups.entrySet()) {
- MergeGroup candidateGroup = entry.getKey();
+ for (Entry<HorizontalMergeGroup, Integer> entry : newGroups.entrySet()) {
+ HorizontalMergeGroup candidateGroup = entry.getKey();
int candidateGroupSize = entry.getValue();
int newCandidateGroupSize = candidateGroupSize + increment;
if (newCandidateGroupSize <= maxGroupSize) {
@@ -53,7 +53,7 @@
}
// Failed to find an existing group.
- newGroups.put(new MergeGroup(clazz), increment);
+ newGroups.put(new HorizontalMergeGroup(clazz), increment);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeInstanceFieldCasts.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeInstanceFieldCasts.java
index 0242766..bfd431f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeInstanceFieldCasts.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/MinimizeInstanceFieldCasts.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
@@ -20,13 +20,13 @@
public class MinimizeInstanceFieldCasts extends MultiClassPolicy {
@Override
- public final Collection<MergeGroup> apply(MergeGroup group) {
+ public final Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
// First find all classes that can be merged without changing field types.
- Map<Multiset<DexType>, MergeGroup> newGroups = new LinkedHashMap<>();
+ Map<Multiset<DexType>, HorizontalMergeGroup> newGroups = new LinkedHashMap<>();
group.forEach(clazz -> addExact(clazz, newGroups));
// Create a single group from all trivial groups.
- MergeGroup pendingGroup = new MergeGroup();
+ HorizontalMergeGroup pendingGroup = new HorizontalMergeGroup();
newGroups
.values()
.removeIf(
@@ -43,13 +43,14 @@
}
if (!pendingGroup.isTrivial()) {
- List<MergeGroup> newGroupsIncludingRelaxedGroup = new ArrayList<>(newGroups.values());
+ List<HorizontalMergeGroup> newGroupsIncludingRelaxedGroup =
+ new ArrayList<>(newGroups.values());
newGroupsIncludingRelaxedGroup.add(pendingGroup);
return newGroupsIncludingRelaxedGroup;
}
- MergeGroup smallestNewGroup = null;
- for (MergeGroup newGroup : newGroups.values()) {
+ HorizontalMergeGroup smallestNewGroup = null;
+ for (HorizontalMergeGroup newGroup : newGroups.values()) {
if (smallestNewGroup == null || newGroup.size() < smallestNewGroup.size()) {
smallestNewGroup = newGroup;
}
@@ -59,8 +60,11 @@
return newGroups.values();
}
- private void addExact(DexProgramClass clazz, Map<Multiset<DexType>, MergeGroup> groups) {
- groups.computeIfAbsent(getExactMergeKey(clazz), ignore -> new MergeGroup()).add(clazz);
+ private void addExact(
+ DexProgramClass clazz, Map<Multiset<DexType>, HorizontalMergeGroup> groups) {
+ groups
+ .computeIfAbsent(getExactMergeKey(clazz), ignore -> new HorizontalMergeGroup())
+ .add(clazz);
}
private Multiset<DexType> getExactMergeKey(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
index bb60289..9addf2f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoClassInitializerCycles.java
@@ -20,7 +20,7 @@
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.lens.GraphLens;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
import com.android.tools.r8.horizontalclassmerging.policies.deadlock.SingleCallerInformation;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -97,7 +97,7 @@
final AppView<AppInfoWithLiveness> appView;
// Mapping from each merge candidate to its merge group.
- final Map<DexProgramClass, MergeGroup> allGroups = new IdentityHashMap<>();
+ final Map<DexProgramClass, HorizontalMergeGroup> allGroups = new IdentityHashMap<>();
private SingleCallerInformation singleCallerInformation;
@@ -112,24 +112,25 @@
// TODO(b/270398965): Replace LinkedList.
@SuppressWarnings("JdkObsolete")
@Override
- public Collection<MergeGroup> apply(MergeGroup group, Void nothing) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group, Void nothing) {
// Partition the merge group into smaller groups that may be merged. If the class initialization
// of a parent class may initialize a member of the merge group, then this member is not
// eligible for class merging, unless the only way to class initialize this member is from the
// class initialization of the parent class. In this case, the member may be merged with other
// group members that are also guaranteed to only be class initialized from the class
// initialization of the parent class.
- List<MergeGroup> partitioning = partitionClassesWithPossibleClassInitializerDeadlock(group);
- List<MergeGroup> newGroups = new LinkedList<>();
+ List<HorizontalMergeGroup> partitioning =
+ partitionClassesWithPossibleClassInitializerDeadlock(group);
+ List<HorizontalMergeGroup> newGroups = new LinkedList<>();
// Revisit each partition. If the class initialization of a group member may initialize another
// class (not necessarily a group member), and vice versa, then class initialization could
// deadlock if the group member is merged with another class that is initialized concurrently.
- for (MergeGroup partition : partitioning) {
- List<MergeGroup> newGroupsFromPartition = new LinkedList<>();
+ for (HorizontalMergeGroup partition : partitioning) {
+ List<HorizontalMergeGroup> newGroupsFromPartition = new LinkedList<>();
Tracer tracer = new Tracer(partition);
for (DexProgramClass clazz : partition) {
- MergeGroup newGroup = getOrCreateGroupFor(clazz, newGroupsFromPartition, tracer);
+ HorizontalMergeGroup newGroup = getOrCreateGroupFor(clazz, newGroupsFromPartition, tracer);
if (newGroup != null) {
newGroup.add(clazz);
} else {
@@ -143,22 +144,22 @@
return newGroups;
}
- private void commit(MergeGroup oldGroup, List<MergeGroup> newGroups) {
- for (MergeGroup newGroup : newGroups) {
+ private void commit(HorizontalMergeGroup oldGroup, List<HorizontalMergeGroup> newGroups) {
+ for (HorizontalMergeGroup newGroup : newGroups) {
for (DexProgramClass member : newGroup) {
allGroups.put(member, newGroup);
}
}
for (DexProgramClass member : oldGroup) {
- MergeGroup newGroup = allGroups.get(member);
+ HorizontalMergeGroup newGroup = allGroups.get(member);
if (newGroup == oldGroup) {
allGroups.remove(member);
}
}
}
- private MergeGroup getOrCreateGroupFor(
- DexProgramClass clazz, List<MergeGroup> groups, Tracer tracer) {
+ private HorizontalMergeGroup getOrCreateGroupFor(
+ DexProgramClass clazz, List<HorizontalMergeGroup> groups, Tracer tracer) {
assert !tracer.hasPossibleClassInitializerDeadlock(clazz);
if (clazz.hasClassInitializer()) {
@@ -174,18 +175,18 @@
}
}
- for (MergeGroup group : groups) {
+ for (HorizontalMergeGroup group : groups) {
if (canMerge(clazz, group, tracer)) {
return group;
}
}
- MergeGroup newGroup = new MergeGroup();
+ HorizontalMergeGroup newGroup = new HorizontalMergeGroup();
groups.add(newGroup);
return newGroup;
}
- private boolean canMerge(DexProgramClass clazz, MergeGroup group, Tracer tracer) {
+ private boolean canMerge(DexProgramClass clazz, HorizontalMergeGroup group, Tracer tracer) {
for (DexProgramClass member : group) {
// Check that the class initialization of the given class cannot reach the class initializer
// of the current group member.
@@ -206,7 +207,8 @@
* If the class initializer of one of the classes in the merge group is reached, then that class
* is not eligible for merging.
*/
- private List<MergeGroup> partitionClassesWithPossibleClassInitializerDeadlock(MergeGroup group) {
+ private List<HorizontalMergeGroup> partitionClassesWithPossibleClassInitializerDeadlock(
+ HorizontalMergeGroup group) {
Set<DexProgramClass> superclasses = Sets.newIdentityHashSet();
appView
.appInfo()
@@ -230,13 +232,15 @@
}
tracer.trace();
- MergeGroup notInitializedByInitializationOfParent = new MergeGroup();
- Map<DexProgramClass, MergeGroup> partitioning = new LinkedHashMap<>();
+ HorizontalMergeGroup notInitializedByInitializationOfParent = new HorizontalMergeGroup();
+ Map<DexProgramClass, HorizontalMergeGroup> partitioning = new LinkedHashMap<>();
for (DexProgramClass member : group) {
if (tracer.hasPossibleClassInitializerDeadlock(member)) {
DexProgramClass nearestLock = getNearestLock(member, superclasses);
if (nearestLock != null) {
- partitioning.computeIfAbsent(nearestLock, ignoreKey(MergeGroup::new)).add(member);
+ partitioning
+ .computeIfAbsent(nearestLock, ignoreKey(HorizontalMergeGroup::new))
+ .add(member);
} else {
// Ineligible for merging.
}
@@ -245,7 +249,7 @@
}
}
- return ImmutableList.<MergeGroup>builder()
+ return ImmutableList.<HorizontalMergeGroup>builder()
.add(notInitializedByInitializationOfParent)
.addAll(partitioning.values())
.build();
@@ -276,9 +280,9 @@
}
@Override
- public Void preprocess(Collection<MergeGroup> groups, ExecutorService executorService)
+ public Void preprocess(Collection<HorizontalMergeGroup> groups, ExecutorService executorService)
throws ExecutionException {
- for (MergeGroup group : groups) {
+ for (HorizontalMergeGroup group : groups) {
for (DexProgramClass clazz : group) {
allGroups.put(clazz, group);
}
@@ -296,7 +300,7 @@
private class Tracer {
- final MergeGroup group;
+ final HorizontalMergeGroup group;
// The members of the existing merge group, for efficient membership querying.
final Set<DexProgramClass> groupMembers;
@@ -314,7 +318,7 @@
// group).
private Collection<DexProgramClass> tracingRoots;
- Tracer(MergeGroup group) {
+ Tracer(HorizontalMergeGroup group) {
this.group = group;
this.groupMembers = SetUtils.newIdentityHashSet(group);
}
@@ -469,7 +473,7 @@
worklist.addIfNotSeen(superClass);
}
- MergeGroup other = allGroups.get(clazz);
+ HorizontalMergeGroup other = allGroups.get(clazz);
if (other != null && other != group) {
worklist.addIfNotSeen(other);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
index 6d83173..f4b929d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoConstructorCollisions.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.IterableUtils;
@@ -55,9 +55,10 @@
* filtered group.
*/
@Override
- public Collection<MergeGroup> apply(MergeGroup group, Set<DexType> collisionResolution) {
- MergeGroup newGroup =
- new MergeGroup(
+ public Collection<HorizontalMergeGroup> apply(
+ HorizontalMergeGroup group, Set<DexType> collisionResolution) {
+ HorizontalMergeGroup newGroup =
+ new HorizontalMergeGroup(
Iterables.filter(group, clazz -> !collisionResolution.contains(clazz.getType())));
return newGroup.isTrivial() ? Collections.emptyList() : ListUtils.newLinkedList(newGroup);
}
@@ -67,10 +68,11 @@
* lead to constructor collisions.
*/
@Override
- public Set<DexType> preprocess(Collection<MergeGroup> groups, ExecutorService executorService) {
+ public Set<DexType> preprocess(
+ Collection<HorizontalMergeGroup> groups, ExecutorService executorService) {
// Build a mapping from types to groups.
- Map<DexType, MergeGroup> groupsByType = new IdentityHashMap<>();
- for (MergeGroup group : groups) {
+ Map<DexType, HorizontalMergeGroup> groupsByType = new IdentityHashMap<>();
+ for (HorizontalMergeGroup group : groups) {
for (DexProgramClass clazz : group) {
groupsByType.put(clazz.getType(), group);
}
@@ -109,7 +111,7 @@
return collisionResolution;
}
- private DexProto rewriteProto(DexProto proto, Map<DexType, MergeGroup> groups) {
+ private DexProto rewriteProto(DexProto proto, Map<DexType, HorizontalMergeGroup> groups) {
DexType[] parameters =
ArrayUtils.map(
proto.getParameters().values,
@@ -118,7 +120,7 @@
return dexItemFactory.createProto(rewriteType(proto.getReturnType(), groups), parameters);
}
- private DexMethod rewriteReference(DexMethod method, Map<DexType, MergeGroup> groups) {
+ private DexMethod rewriteReference(DexMethod method, Map<DexType, HorizontalMergeGroup> groups) {
return dexItemFactory.createMethod(
rewriteType(method.getHolderType(), groups),
rewriteProto(method.getProto(), groups),
@@ -126,7 +128,7 @@
}
@SuppressWarnings("ReferenceEquality")
- private DexType rewriteType(DexType type, Map<DexType, MergeGroup> groups) {
+ private DexType rewriteType(DexType type, Map<DexType, HorizontalMergeGroup> groups) {
if (type.isArrayType()) {
DexType baseType = type.toBaseType(dexItemFactory);
DexType rewrittenBaseType = rewriteType(baseType, groups);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java
index 7e32fd3..ee9f3a7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDeadLocks.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Collection;
@@ -28,14 +28,14 @@
// TODO(b/270398965): Replace LinkedList.
@Override
@SuppressWarnings({"JdkObsolete", "MixedMutabilityReturnType"})
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
// Gather all synchronized classes.
- Collection<MergeGroup> synchronizedGroups = new LinkedList<>();
+ Collection<HorizontalMergeGroup> synchronizedGroups = new LinkedList<>();
group.removeIf(
clazz -> {
boolean synchronizationClass = isSynchronizationClass(clazz);
if (synchronizationClass) {
- MergeGroup synchronizedGroup = new MergeGroup();
+ HorizontalMergeGroup synchronizedGroup = new HorizontalMergeGroup();
synchronizedGroup.add(clazz);
synchronizedGroups.add(synchronizedGroup);
}
@@ -46,7 +46,7 @@
return Collections.singletonList(group);
}
- Iterator<MergeGroup> synchronizedGroupIterator = synchronizedGroups.iterator();
+ Iterator<HorizontalMergeGroup> synchronizedGroupIterator = synchronizedGroups.iterator();
for (DexProgramClass clazz : group) {
if (!synchronizedGroupIterator.hasNext()) {
synchronizedGroupIterator = synchronizedGroups.iterator();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
index e8494a3..f330807 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
@@ -19,7 +19,7 @@
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodCollisions.InterfaceInfo;
import com.android.tools.r8.utils.IterableUtils;
@@ -82,7 +82,8 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group, Map<DexType, InterfaceInfo> infos) {
+ public Collection<HorizontalMergeGroup> apply(
+ HorizontalMergeGroup group, Map<DexType, InterfaceInfo> infos) {
if (!group.isInterfaceGroup()) {
return ImmutableList.of(group);
}
@@ -95,7 +96,7 @@
// TODO(b/173990042): Consider forming multiple groups instead of just filtering. In practice,
// this rarely leads to much filtering, though, since the use of default methods is somewhat
// limited.
- MergeGroup newGroup = new MergeGroup();
+ HorizontalMergeGroup newGroup = new HorizontalMergeGroup();
for (DexProgramClass clazz : group) {
Set<DexMethod> newDefaultMethodsAddedToClassByMerge =
computeNewDefaultMethodsAddedToClassByMerge(clazz, group, infos);
@@ -107,7 +108,7 @@
}
private Set<DexMethod> computeNewDefaultMethodsAddedToClassByMerge(
- DexProgramClass clazz, MergeGroup group, Map<DexType, InterfaceInfo> infos) {
+ DexProgramClass clazz, HorizontalMergeGroup group, Map<DexType, InterfaceInfo> infos) {
// Run through the other classes in the merge group, and add the default interface methods that
// they declare (or inherit from a super interface) to a set.
Set<DexMethod> newDefaultMethodsAddedToClassByMerge = Sets.newIdentityHashSet();
@@ -146,7 +147,7 @@
@Override
public Map<DexType, InterfaceInfo> preprocess(
- Collection<MergeGroup> groups, ExecutorService executorService) {
+ Collection<HorizontalMergeGroup> groups, ExecutorService executorService) {
SubtypingInfo subtypingInfo = SubtypingInfo.create(appView);
Collection<DexProgramClass> classesOfInterest = computeClassesOfInterest(subtypingInfo);
Map<DexType, DexMethodSignatureSet> inheritedClassMethodsPerClass =
@@ -163,7 +164,7 @@
// Store the computed information for each interface that is subject to merging.
Map<DexType, InterfaceInfo> infos = new IdentityHashMap<>();
- for (MergeGroup group : groups) {
+ for (HorizontalMergeGroup group : groups) {
if (group.isInterfaceGroup()) {
for (DexProgramClass clazz : group) {
infos.put(
@@ -277,13 +278,13 @@
computeDefaultMethodsInheritedBySubclassesPerProgramClass(
Collection<DexProgramClass> classesOfInterest,
Map<DexType, Map<DexMethodSignature, Set<DexMethod>>> inheritedDefaultMethodsPerClass,
- Collection<MergeGroup> groups,
+ Collection<HorizontalMergeGroup> groups,
SubtypingInfo subtypingInfo) {
// Build a mapping from class types to their merge group.
Map<DexType, Iterable<DexProgramClass>> classGroupsByType =
MapUtils.newIdentityHashMap(
builder ->
- Iterables.filter(groups, MergeGroup::isClassGroup)
+ Iterables.filter(groups, HorizontalMergeGroup::isClassGroup)
.forEach(group -> group.forEach(clazz -> builder.put(clazz.getType(), group))));
// Copy the map from classes to their inherited default methods.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodMerging.java
index e466650..318a2af 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodMerging.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.utils.WorkList;
import com.android.tools.r8.utils.collections.DexMethodSignatureMap;
@@ -43,11 +43,11 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
// Split the group into smaller groups such that no default methods collide.
// TODO(b/229951607): This fixes the ICCE issue for synthetic lambda classes, but a more
// general solution possibly extending the policy NoDefaultInterfaceMethodCollisions.
- Map<MergeGroup, DexMethodSignatureMap<DexType>> newGroups = new LinkedHashMap<>();
+ Map<HorizontalMergeGroup, DexMethodSignatureMap<DexType>> newGroups = new LinkedHashMap<>();
for (DexProgramClass clazz : group) {
addClassToGroup(
clazz,
@@ -63,14 +63,14 @@
@SuppressWarnings("ReferenceEquality")
private void addClassToGroup(
DexProgramClass clazz,
- Map<MergeGroup, DexMethodSignatureMap<DexType>> newGroups,
+ Map<HorizontalMergeGroup, DexMethodSignatureMap<DexType>> newGroups,
Function<DexProgramClass, DexMethodSignatureMap<DexType>> fn) {
DexMethodSignatureMap<DexType> classSignatures = fn.apply(clazz);
// Find a group that does not have any collisions with `clazz`.
nextGroup:
- for (Entry<MergeGroup, DexMethodSignatureMap<DexType>> entry : newGroups.entrySet()) {
- MergeGroup group = entry.getKey();
+ for (Entry<HorizontalMergeGroup, DexMethodSignatureMap<DexType>> entry : newGroups.entrySet()) {
+ HorizontalMergeGroup group = entry.getKey();
DexMethodSignatureMap<DexType> groupSignatures = entry.getValue();
if (!groupSignatures.containsAnyKeyOf(classSignatures.keySet())) {
groupSignatures.putAll(classSignatures);
@@ -92,7 +92,7 @@
}
// Else create a new group.
- newGroups.put(new MergeGroup(clazz), classSignatures);
+ newGroups.put(new HorizontalMergeGroup(clazz), classSignatures);
}
@SuppressWarnings("ReferenceEquality")
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
index aef65d3..b792e84 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInstanceInitializerMerging.java
@@ -19,13 +19,13 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.horizontalclassmerging.ClassInstanceFieldsMerger;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.IRCodeProvider;
import com.android.tools.r8.horizontalclassmerging.InstanceInitializerAnalysis;
import com.android.tools.r8.horizontalclassmerging.InstanceInitializerAnalysis.AbsentInstanceInitializer;
import com.android.tools.r8.horizontalclassmerging.InstanceInitializerAnalysis.InstanceInitializer;
import com.android.tools.r8.horizontalclassmerging.InstanceInitializerAnalysis.PresentInstanceInitializer;
import com.android.tools.r8.horizontalclassmerging.InstanceInitializerDescription;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ListUtils;
@@ -73,7 +73,7 @@
@Override
@SuppressWarnings("MixedMutabilityReturnType")
public Map<DexProgramClass, Set<DexMethod>> preprocess(
- Collection<MergeGroup> groups, ExecutorService executorService) {
+ Collection<HorizontalMergeGroup> groups, ExecutorService executorService) {
if (!appView.options().canHaveNonReboundConstructorInvoke()) {
return Collections.emptyMap();
}
@@ -118,8 +118,8 @@
@Override
@SuppressWarnings("MixedMutabilityReturnType")
- public Collection<MergeGroup> apply(
- MergeGroup group, Map<DexProgramClass, Set<DexMethod>> absentInstanceInitializers) {
+ public Collection<HorizontalMergeGroup> apply(
+ HorizontalMergeGroup group, Map<DexProgramClass, Set<DexMethod>> absentInstanceInitializers) {
assert !group.hasTarget();
assert !group.hasInstanceFieldMap();
@@ -148,7 +148,8 @@
group.selectTarget(appView);
group.selectInstanceFieldMap(appView);
- Map<MergeGroup, Map<DexMethodSignature, InstanceInitializer>> newGroups = new LinkedHashMap<>();
+ Map<HorizontalMergeGroup, Map<DexMethodSignature, InstanceInitializer>> newGroups =
+ new LinkedHashMap<>();
// Caching of instance initializer descriptions, which are used to determine equivalence.
// TODO(b/181846319): Make this cache available to the instance initializer merger so that we
@@ -164,12 +165,12 @@
// Partition group into smaller groups where there are no (non-equivalent) instance initializer
// collisions.
for (DexProgramClass clazz : group) {
- MergeGroup newGroup = null;
+ HorizontalMergeGroup newGroup = null;
Map<DexMethodSignature, InstanceInitializer> classInstanceInitializers =
getInstanceInitializersByRelaxedSignature(clazz, absentInstanceInitializers);
- for (Entry<MergeGroup, Map<DexMethodSignature, InstanceInitializer>> entry :
+ for (Entry<HorizontalMergeGroup, Map<DexMethodSignature, InstanceInitializer>> entry :
newGroups.entrySet()) {
- MergeGroup candidateGroup = entry.getKey();
+ HorizontalMergeGroup candidateGroup = entry.getKey();
Map<DexMethodSignature, InstanceInitializer> groupInstanceInitializers = entry.getValue();
if (canAddClassToGroup(
classInstanceInitializers,
@@ -183,12 +184,12 @@
if (newGroup != null) {
newGroup.add(clazz);
} else {
- newGroups.put(new MergeGroup(clazz), classInstanceInitializers);
+ newGroups.put(new HorizontalMergeGroup(clazz), classInstanceInitializers);
}
}
// Remove trivial groups and finalize the newly created groups.
- Collection<MergeGroup> newNonTrivialGroups = removeTrivialGroups(newGroups.keySet());
+ Collection<HorizontalMergeGroup> newNonTrivialGroups = removeTrivialGroups(newGroups.keySet());
setInstanceFieldMaps(newNonTrivialGroups, group);
return newNonTrivialGroups;
}
@@ -265,7 +266,7 @@
}
private Optional<InstanceInitializerDescription> getOrComputeInstanceInitializerDescription(
- MergeGroup group,
+ HorizontalMergeGroup group,
InstanceInitializer instanceInitializer,
Map<DexMethod, Optional<InstanceInitializerDescription>> instanceInitializerDescriptions) {
return instanceInitializerDescriptions.computeIfAbsent(
@@ -296,8 +297,9 @@
: instanceInitializerReference;
}
- private void setInstanceFieldMaps(Iterable<MergeGroup> newGroups, MergeGroup group) {
- for (MergeGroup newGroup : newGroups) {
+ private void setInstanceFieldMaps(
+ Iterable<HorizontalMergeGroup> newGroups, HorizontalMergeGroup group) {
+ for (HorizontalMergeGroup newGroup : newGroups) {
// Set target.
newGroup.selectTarget(appView);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
index 07b2fc7..21283e1 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoVirtualMethodMerging.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.SetUtils;
@@ -40,17 +40,19 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
- Map<MergeGroup, Map<DexMethodSignature, ProgramMethod>> newGroups = new LinkedHashMap<>();
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
+ Map<HorizontalMergeGroup, Map<DexMethodSignature, ProgramMethod>> newGroups =
+ new LinkedHashMap<>();
for (DexProgramClass clazz : group) {
Map<DexMethodSignature, ProgramMethod> classMethods = new HashMap<>();
clazz.forEachProgramVirtualMethodMatching(
DexEncodedMethod::isNonAbstractVirtualMethod,
method -> classMethods.put(method.getMethodSignature(), method));
- MergeGroup newGroup = null;
- for (Entry<MergeGroup, Map<DexMethodSignature, ProgramMethod>> entry : newGroups.entrySet()) {
- MergeGroup candidateGroup = entry.getKey();
+ HorizontalMergeGroup newGroup = null;
+ for (Entry<HorizontalMergeGroup, Map<DexMethodSignature, ProgramMethod>> entry :
+ newGroups.entrySet()) {
+ HorizontalMergeGroup candidateGroup = entry.getKey();
Map<DexMethodSignature, ProgramMethod> groupMethods = entry.getValue();
if (canAddNonAbstractVirtualMethodsToGroup(
clazz, classMethods.values(), candidateGroup, groupMethods)) {
@@ -63,7 +65,7 @@
if (newGroup != null) {
newGroup.add(clazz);
} else {
- newGroups.put(new MergeGroup(clazz), classMethods);
+ newGroups.put(new HorizontalMergeGroup(clazz), classMethods);
}
}
return removeTrivialGroups(newGroups.keySet());
@@ -72,7 +74,7 @@
private boolean canAddNonAbstractVirtualMethodsToGroup(
DexProgramClass clazz,
Collection<ProgramMethod> methods,
- MergeGroup group,
+ HorizontalMergeGroup group,
Map<DexMethodSignature, ProgramMethod> groupMethods) {
// For each of clazz' virtual methods, check that adding these methods to the group does not
// require method merging.
@@ -92,7 +94,8 @@
return true;
}
- private boolean hasNonAbstractDefinitionInHierarchy(MergeGroup group, ProgramMethod method) {
+ private boolean hasNonAbstractDefinitionInHierarchy(
+ HorizontalMergeGroup group, ProgramMethod method) {
return hasNonAbstractDefinitionInSuperClass(group.getSuperType(), method)
|| hasNonAbstractDefinitionInSuperInterface(
SetUtils.newIdentityHashSet(IterableUtils.flatMap(group, DexClass::getInterfaces)),
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoWeakerAccessPrivileges.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoWeakerAccessPrivileges.java
index 2305438..3daa113 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoWeakerAccessPrivileges.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoWeakerAccessPrivileges.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
@@ -46,14 +46,14 @@
// TODO(b/270398965): Replace LinkedList.
@SuppressWarnings("JdkObsolete")
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
- List<MergeGroup> newMergeGroups = new LinkedList<>();
- Map<MergeGroup, DexMethodSignatureSet> inheritedInterfaceMethodsPerGroup =
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
+ List<HorizontalMergeGroup> newMergeGroups = new LinkedList<>();
+ Map<HorizontalMergeGroup, DexMethodSignatureSet> inheritedInterfaceMethodsPerGroup =
new IdentityHashMap<>();
for (DexProgramClass clazz : group) {
// Find an existing merge group that the current class can be added to.
- MergeGroup newMergeGroup = null;
- for (MergeGroup candidateMergeGroup : newMergeGroups) {
+ HorizontalMergeGroup newMergeGroup = null;
+ for (HorizontalMergeGroup candidateMergeGroup : newMergeGroups) {
DexMethodSignatureSet inheritedInterfaceMethodsInGroup =
inheritedInterfaceMethodsPerGroup.get(candidateMergeGroup);
if (canAddToGroup(clazz, candidateMergeGroup, inheritedInterfaceMethodsInGroup)) {
@@ -65,7 +65,7 @@
DexMethodSignatureSet inheritedInterfaceMethodsInGroup;
if (newMergeGroup == null) {
// Form a new singleton merge group from the current class.
- newMergeGroup = new MergeGroup(clazz);
+ newMergeGroup = new HorizontalMergeGroup(clazz);
newMergeGroups.add(newMergeGroup);
inheritedInterfaceMethodsInGroup = DexMethodSignatureSet.create();
inheritedInterfaceMethodsPerGroup.put(newMergeGroup, inheritedInterfaceMethodsInGroup);
@@ -86,7 +86,7 @@
private boolean canAddToGroup(
DexProgramClass clazz,
- MergeGroup group,
+ HorizontalMergeGroup group,
DexMethodSignatureSet inheritedInterfaceMethodsInGroup) {
// We need to ensure that adding class to the group is OK.
DexMethodSignatureSet nonPublicVirtualMethodSignaturesInClassComponent =
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
index 9812a26..db74f25 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/OnlyDirectlyConnectedOrUnrelatedInterfaces.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicyWithPreprocessing;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.WorkList;
@@ -62,7 +62,7 @@
private final Mode mode;
// The interface merge groups that this policy has committed to so far.
- private final Map<DexProgramClass, MergeGroup> committed = new IdentityHashMap<>();
+ private final Map<DexProgramClass, HorizontalMergeGroup> committed = new IdentityHashMap<>();
public OnlyDirectlyConnectedOrUnrelatedInterfaces(
AppView<? extends AppInfoWithClassHierarchy> appView, Mode mode) {
@@ -73,7 +73,8 @@
// TODO(b/270398965): Replace LinkedList.
@Override
@SuppressWarnings({"JdkObsolete", "MixedMutabilityReturnType"})
- public Collection<MergeGroup> apply(MergeGroup group, SubtypingInfo subtypingInfo) {
+ public Collection<HorizontalMergeGroup> apply(
+ HorizontalMergeGroup group, SubtypingInfo subtypingInfo) {
if (!group.isInterfaceGroup()) {
return ImmutableList.of(group);
}
@@ -104,9 +105,9 @@
committed.put(clazz, newGroup.getGroup());
}
- List<MergeGroup> newGroups = new LinkedList<>();
+ List<HorizontalMergeGroup> newGroups = new LinkedList<>();
for (MergeGroupWithInfo newGroupWithInfo : newGroupsWithInfo) {
- MergeGroup newGroup = newGroupWithInfo.getGroup();
+ HorizontalMergeGroup newGroup = newGroupWithInfo.getGroup();
if (newGroup.isTrivial()) {
assert !newGroup.isEmpty();
committed.remove(newGroup.getClasses().getFirst());
@@ -135,7 +136,7 @@
workList.addIgnoringSeenSet(clazz);
workList.process(
interfaceDefinition -> {
- MergeGroup group = committed.get(interfaceDefinition);
+ HorizontalMergeGroup group = committed.get(interfaceDefinition);
if (group != null) {
workList.addIfNotSeen(group);
}
@@ -163,7 +164,8 @@
}
@Override
- public SubtypingInfo preprocess(Collection<MergeGroup> groups, ExecutorService executorService) {
+ public SubtypingInfo preprocess(
+ Collection<HorizontalMergeGroup> groups, ExecutorService executorService) {
return SubtypingInfo.create(appView);
}
@@ -174,7 +176,7 @@
static class MergeGroupWithInfo {
- private final MergeGroup group;
+ private final HorizontalMergeGroup group;
private final Set<DexProgramClass> members;
private final Set<DexProgramClass> superInterfaces;
private final Set<DexProgramClass> subInterfaces;
@@ -183,7 +185,7 @@
DexProgramClass clazz,
Set<DexProgramClass> superInterfaces,
Set<DexProgramClass> subInterfaces) {
- this.group = new MergeGroup(clazz);
+ this.group = new HorizontalMergeGroup(clazz);
this.members = SetUtils.newIdentityHashSet(clazz);
this.superInterfaces = superInterfaces;
this.subInterfaces = subInterfaces;
@@ -206,7 +208,7 @@
subInterfaces.remove(clazz);
}
- MergeGroup getGroup() {
+ HorizontalMergeGroup getGroup() {
return group;
}
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 63e334b..b891bcd 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
@@ -10,7 +10,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.OptionalBool;
@@ -101,10 +101,10 @@
public static class TargetGroup {
- private final MergeGroup group = new MergeGroup();
+ private final HorizontalMergeGroup group = new HorizontalMergeGroup();
private final Map<DexMethodSignature, MethodCharacteristics> methodMap = new HashMap<>();
- public MergeGroup getGroup() {
+ public HorizontalMergeGroup getGroup() {
return group;
}
@@ -139,7 +139,7 @@
// TODO(b/270398965): Replace LinkedList.
@SuppressWarnings("JdkObsolete")
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
List<TargetGroup> groups = new ArrayList<>();
for (DexProgramClass clazz : group) {
@@ -152,7 +152,7 @@
}
}
- LinkedList<MergeGroup> newGroups = new LinkedList<>();
+ LinkedList<HorizontalMergeGroup> newGroups = new LinkedList<>();
for (TargetGroup newGroup : groups) {
if (!newGroup.getGroup().isTrivial()) {
newGroups.add(newGroup.getGroup());
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
index 2a3602c..06e39d7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
import com.google.common.collect.ImmutableList;
@@ -151,7 +151,7 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
// This policy is specific to issues that may arise from merging (non-interface) classes.
if (group.isInterfaceGroup()) {
return ImmutableList.of(group);
@@ -162,7 +162,7 @@
signatures.addAllMethods(clazz.methods());
}
- Map<DispatchSignature, MergeGroup> newGroups = new LinkedHashMap<>();
+ Map<DispatchSignature, HorizontalMergeGroup> newGroups = new LinkedHashMap<>();
for (DexProgramClass clazz : group) {
DexMethodSignatureSet clazzReserved = computeReservedSignaturesForClass(clazz);
DispatchSignature dispatchSignature = new DispatchSignature();
@@ -178,7 +178,7 @@
}
dispatchSignature.addSignature(signature, category);
}
- newGroups.computeIfAbsent(dispatchSignature, ignore -> new MergeGroup()).add(clazz);
+ newGroups.computeIfAbsent(dispatchSignature, ignore -> new HorizontalMergeGroup()).add(clazz);
}
return removeTrivialGroups(newGroups.values());
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
index 29a9147..bfc4cd1 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger.Mode;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.verticalclassmerging.IllegalAccessDetector;
@@ -122,10 +122,12 @@
/** Sort unrestricted classes into restricted classes if they are in the same package. */
void tryFindRestrictedPackage(
- MergeGroup unrestrictedClasses, Map<String, MergeGroup> restrictedClasses) {
+ HorizontalMergeGroup unrestrictedClasses,
+ Map<String, HorizontalMergeGroup> restrictedClasses) {
unrestrictedClasses.removeIf(
clazz -> {
- MergeGroup restrictedPackage = restrictedClasses.get(clazz.type.getPackageDescriptor());
+ HorizontalMergeGroup restrictedPackage =
+ restrictedClasses.get(clazz.type.getPackageDescriptor());
if (restrictedPackage != null) {
restrictedPackage.add(clazz);
return true;
@@ -135,15 +137,16 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
- Map<String, MergeGroup> restrictedClasses = new LinkedHashMap<>();
- MergeGroup unrestrictedClasses = new MergeGroup();
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
+ Map<String, HorizontalMergeGroup> restrictedClasses = new LinkedHashMap<>();
+ HorizontalMergeGroup unrestrictedClasses = new HorizontalMergeGroup();
// Sort all restricted classes into packages.
for (DexProgramClass clazz : group) {
if (shouldRestrictMergingAcrossPackageBoundary(clazz)) {
restrictedClasses
- .computeIfAbsent(clazz.getType().getPackageDescriptor(), ignore -> new MergeGroup())
+ .computeIfAbsent(
+ clazz.getType().getPackageDescriptor(), ignore -> new HorizontalMergeGroup())
.add(clazz);
} else {
unrestrictedClasses.add(clazz);
@@ -155,7 +158,7 @@
// TODO(b/166577694): Add the unrestricted classes to restricted groups, but ensure they aren't
// the merge target.
- Collection<MergeGroup> groups = new ArrayList<>(restrictedClasses.size() + 1);
+ Collection<HorizontalMergeGroup> groups = new ArrayList<>(restrictedClasses.size() + 1);
if (unrestrictedClasses.size() > 1) {
groups.add(unrestrictedClasses);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForNonGlobalMergeSynthetic.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForNonGlobalMergeSynthetic.java
index db6d282..a95bafe 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForNonGlobalMergeSynthetic.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SamePackageForNonGlobalMergeSynthetic.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.google.common.collect.Iterables;
@@ -28,10 +28,12 @@
/** Sort unrestricted classes into restricted classes if they are in the same package. */
private void tryFindRestrictedPackage(
- MergeGroup unrestrictedClasses, Map<String, MergeGroup> restrictedClasses) {
+ HorizontalMergeGroup unrestrictedClasses,
+ Map<String, HorizontalMergeGroup> restrictedClasses) {
unrestrictedClasses.removeIf(
clazz -> {
- MergeGroup restrictedPackage = restrictedClasses.get(clazz.type.getPackageDescriptor());
+ HorizontalMergeGroup restrictedPackage =
+ restrictedClasses.get(clazz.type.getPackageDescriptor());
if (restrictedPackage != null) {
restrictedPackage.add(clazz);
return true;
@@ -41,9 +43,9 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
- Map<String, MergeGroup> restrictedClasses = new LinkedHashMap<>();
- MergeGroup unrestrictedClasses = new MergeGroup();
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
+ Map<String, HorizontalMergeGroup> restrictedClasses = new LinkedHashMap<>();
+ HorizontalMergeGroup unrestrictedClasses = new HorizontalMergeGroup();
SyntheticItems syntheticItems = appView.getSyntheticItems();
// Sort all restricted classes into packages.
@@ -56,7 +58,7 @@
|| !kind.asSyntheticMethodKind().isAllowGlobalMerging())) {
restrictedClasses
.computeIfAbsent(
- clazz.getType().getPackageDescriptor(), ignoreArgument(MergeGroup::new))
+ clazz.getType().getPackageDescriptor(), ignoreArgument(HorizontalMergeGroup::new))
.add(clazz);
} else {
unrestrictedClasses.add(clazz);
@@ -66,7 +68,7 @@
tryFindRestrictedPackage(unrestrictedClasses, restrictedClasses);
removeTrivialGroups(restrictedClasses.values());
- Collection<MergeGroup> groups = new ArrayList<>(restrictedClasses.size() + 1);
+ Collection<HorizontalMergeGroup> groups = new ArrayList<>(restrictedClasses.size() + 1);
if (unrestrictedClasses.size() > 1) {
groups.add(unrestrictedClasses);
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java
index 7ad1e60..e62195b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/VerifyMultiClassPolicyAlwaysSatisfied.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.horizontalclassmerging.policies;
-import com.android.tools.r8.horizontalclassmerging.MergeGroup;
+import com.android.tools.r8.horizontalclassmerging.HorizontalMergeGroup;
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Collection;
@@ -29,15 +29,15 @@
}
@Override
- public Collection<MergeGroup> apply(MergeGroup group) {
+ public Collection<HorizontalMergeGroup> apply(HorizontalMergeGroup group) {
assert verifySameAppliedGroup(group);
return Collections.singletonList(group);
}
- private boolean verifySameAppliedGroup(MergeGroup group) {
- Collection<MergeGroup> applied = policy.apply(group);
+ private boolean verifySameAppliedGroup(HorizontalMergeGroup group) {
+ Collection<HorizontalMergeGroup> applied = policy.apply(group);
assert applied.size() == 1;
- MergeGroup appliedGroup = applied.iterator().next();
+ HorizontalMergeGroup appliedGroup = applied.iterator().next();
assert appliedGroup.size() == group.size() && group.containsAll(appliedGroup);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 1b14342..1ef539a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -445,13 +445,14 @@
if (instanceGet.instructionMayHaveSideEffects(appView, context)) {
return false;
}
+ NewInstance newInstance = null;
if (instanceGet.object().isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
- NewInstance newInstance = instanceGet.object().getDefinition().asNewInstance();
+ newInstance = instanceGet.object().getDefinition().asNewInstance();
if (newInstance.getUniqueConstructorInvoke(appView.dexItemFactory()) == null) {
return false;
}
}
- if (!isReadOfEffectivelyFinalFieldOutsideInitializer(instanceGet)) {
+ if (!isReadOfEffectivelyFinalFieldOutsideInitializer(instanceGet, newInstance)) {
return false;
}
if (getOrComputeIneligibleInstanceGetInstructions().contains(instanceGet)) {
@@ -484,7 +485,12 @@
return true;
}
- private boolean isReadOfEffectivelyFinalFieldOutsideInitializer(FieldGet fieldGet) {
+ private boolean isReadOfEffectivelyFinalFieldOutsideInitializer(StaticGet staticGet) {
+ return isReadOfEffectivelyFinalFieldOutsideInitializer(staticGet, null);
+ }
+
+ private boolean isReadOfEffectivelyFinalFieldOutsideInitializer(
+ FieldGet fieldGet, NewInstance newInstance) {
if (getOrComputeIsAccessingVolatileField()) {
// A final field may be initialized concurrently. A requirement for this is that the field is
// volatile. However, the reading or writing of another volatile field also allows for
@@ -512,6 +518,14 @@
if (!resolvedField.isFinalOrEffectivelyFinal(appViewWithClassHierarchy)) {
return false;
}
+ if (!resolvedField.getAccessFlags().isFinal() && newInstance != null) {
+ // The effectively final property captured in the enqueuer may be invalidated by constructor
+ // inlining (in particular, fields that used only to be written in instance initializers from
+ // the enclosing class may now be written outside such constructors). If we see an
+ // instance-get on a newly created instance, we therefore bail-out since the field may in
+ // principle not be effectively final in this method.
+ return false;
+ }
if (appView.getKeepInfo(resolvedField).isPinned(appView.options())) {
// The final flag could be unset using reflection.
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
index bd524a5..e1e605d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
@@ -53,9 +53,9 @@
return;
}
if (argumentInfo.isRewrittenTypeInfo()
- && argumentInfo.asRewrittenTypeInfo().getNewType().isIntType()) {
- // This is due to enum unboxing. After enum unboxing, we no longer need information
- // about the usages of this parameter for class inlining.
+ && argumentInfo.asRewrittenTypeInfo().getNewType().isPrimitiveType()) {
+ // This is due to number/enum unboxing. After enum unboxing, we no longer need
+ // information about the usages of this parameter for class inlining.
return;
}
backing.put(changes.getNewArgumentIndex(argumentIndex), usagePerContext);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index c0a7655..5f61f9b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -73,15 +73,13 @@
}
List<DexProgramClass> subtypes = subtypingInfo.getSubclasses(clazz);
- ImmutableSet.Builder<DexProgramClass> subEnumClassesBuilder = ImmutableSet.builder();
for (DexProgramClass subEnum : subtypes) {
if (!isSubEnumUnboxingCandidate(subEnum)) {
return;
}
- subEnumClassesBuilder.add(subEnum);
}
enumToUnboxCandidates.addCandidate(
- appView, clazz, subEnumClassesBuilder.build(), graphLensForPrimaryOptimizationPass);
+ appView, clazz, ImmutableSet.copyOf(subtypes), graphLensForPrimaryOptimizationPass);
}
@SuppressWarnings("ReferenceEquality")
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 01b4a7d..3314582 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -94,7 +94,8 @@
parameterChanges.getArgumentInfo(parameterIndex).asRewrittenTypeInfo();
if (rewrittenTypeInfo != null
&& rewrittenTypeInfo.getOldType().isReferenceType()
- && rewrittenTypeInfo.getNewType().isIntType()) {
+ && rewrittenTypeInfo.getNewType().isPrimitiveType()) {
+ // Clear information for number/enum unboxing.
rewrittenParameterIndex++;
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfoFixer.java
index a593d91..c90d850 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfoFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfoFixer.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintFactory;
+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.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
@@ -74,4 +76,15 @@
public BitSet fixupArguments(BitSet arguments) {
return arguments;
}
+
+ @Override
+ public DynamicType fixupDynamicType(DynamicType dynamicType) {
+ return dynamicType;
+ }
+
+ @Override
+ public AbstractValue fixupAbstractReturnValue(
+ AppView<AppInfoWithLiveness> appView, AbstractValue returnValue) {
+ return returnValue;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
index 4279b3c..06ef441 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoFixer.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraint;
import com.android.tools.r8.ir.analysis.inlining.SimpleInliningConstraintFactory;
+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.classinliner.constraint.ClassInlinerMethodConstraint;
import com.android.tools.r8.ir.optimize.enums.classification.EnumUnboxerMethodClassification;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
@@ -44,4 +46,9 @@
SimpleInliningConstraintFactory factory);
public abstract BitSet fixupArguments(BitSet arguments);
+
+ public abstract DynamicType fixupDynamicType(DynamicType dynamicType);
+
+ public abstract AbstractValue fixupAbstractReturnValue(
+ AppView<AppInfoWithLiveness> appView, AbstractValue returnValue);
}
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 685fc0f..602b555 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
@@ -159,6 +159,8 @@
return fixupArgumentInfos(method, fixer)
.fixupBridgeInfo(fixer)
.fixupClassInlinerMethodConstraint(appView, fixer)
+ .fixupDynamicType(fixer)
+ .fixupAbstractReturnValue(appView, fixer)
.fixupEnumUnboxerMethodClassification(fixer)
.fixupInstanceInitializerInfo(appView, fixer)
.fixupNonNullParamOnNormalExits(fixer)
@@ -169,6 +171,13 @@
.fixupUnusedArguments(fixer);
}
+ private MutableMethodOptimizationInfo fixupDynamicType(MethodOptimizationInfoFixer fixer) {
+ if (dynamicType.isUnknown()) {
+ return this;
+ }
+ return setDynamicType(fixer.fixupDynamicType(dynamicType));
+ }
+
public MutableMethodOptimizationInfo fixupClassTypeReferences(
AppView<AppInfoWithLiveness> appView, GraphLens lens) {
return fixupClassTypeReferences(appView, lens, emptySet());
@@ -202,6 +211,15 @@
}
public MutableMethodOptimizationInfo fixupAbstractReturnValue(
+ AppView<AppInfoWithLiveness> appView, MethodOptimizationInfoFixer fixer) {
+ if (abstractReturnValue.isUnknown()) {
+ return this;
+ }
+ abstractReturnValue = fixer.fixupAbstractReturnValue(appView, abstractReturnValue);
+ return this;
+ }
+
+ public MutableMethodOptimizationInfo fixupAbstractReturnValue(
AppView<AppInfoWithLiveness> appView,
DexEncodedMethod method,
GraphLens lens,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java
new file mode 100644
index 0000000..db81e4b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ClassOptimizer.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2023, 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.library;
+
+import com.android.tools.r8.graph.AppView;
+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.DexType;
+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.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Set;
+
+public class ClassOptimizer extends StatelessLibraryMethodModelCollection {
+
+ private final InternalOptions options;
+ private final DexItemFactory dexItemFactory;
+ private final DexMethod getConstructor;
+ private final DexMethod getDeclaredConstructor;
+ private final DexMethod getMethod;
+ private final DexMethod getDeclaredMethod;
+
+ ClassOptimizer(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.options = appView.options();
+ this.dexItemFactory = dexItemFactory;
+ getConstructor = dexItemFactory.classMethods.getConstructor;
+ getDeclaredConstructor = dexItemFactory.classMethods.getDeclaredConstructor;
+ getMethod = dexItemFactory.classMethods.getMethod;
+ getDeclaredMethod = dexItemFactory.classMethods.getDeclaredMethod;
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.classType;
+ }
+
+ @Override
+ @SuppressWarnings("ReferenceEquality")
+ public InstructionListIterator optimize(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ DexMethod singleTargetReference = singleTarget.getReference();
+ if (singleTargetReference.isIdenticalTo(getConstructor)
+ || singleTargetReference.isIdenticalTo(getDeclaredConstructor)
+ || singleTargetReference.isIdenticalTo(getMethod)
+ || singleTargetReference.isIdenticalTo(getDeclaredMethod)) {
+ EmptyVarargsUtil.replaceWithNullIfEmptyArray(
+ invoke.getLastArgument(), code, instructionIterator, options, affectedValues);
+ assert instructionIterator.peekPrevious() == invoke;
+ }
+ return instructionIterator;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ConstructorOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ConstructorOptimizer.java
new file mode 100644
index 0000000..718ed8b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ConstructorOptimizer.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2023, 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.library;
+
+import com.android.tools.r8.graph.AppView;
+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.DexType;
+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.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Set;
+
+public class ConstructorOptimizer extends StatelessLibraryMethodModelCollection {
+
+ private final InternalOptions options;
+ private final DexItemFactory dexItemFactory;
+ private final DexMethod newInstance;
+
+ ConstructorOptimizer(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.options = appView.options();
+ this.dexItemFactory = dexItemFactory;
+ newInstance = dexItemFactory.constructorMethods.newInstance;
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.constructorType;
+ }
+
+ @Override
+ @SuppressWarnings("ReferenceEquality")
+ public InstructionListIterator optimize(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ DexMethod singleTargetReference = singleTarget.getReference();
+ if (singleTargetReference.isIdenticalTo(newInstance)) {
+ EmptyVarargsUtil.replaceWithNullIfEmptyArray(
+ invoke.getArgument(1), code, instructionIterator, options, affectedValues);
+ assert instructionIterator.peekPrevious() == invoke;
+ }
+ return instructionIterator;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsUtil.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsUtil.java
new file mode 100644
index 0000000..a81a0c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsUtil.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, 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.library;
+
+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.Value;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.InternalOptions;
+
+public class EmptyVarargsUtil {
+ public static void replaceWithNullIfEmptyArray(
+ Value value,
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InternalOptions options,
+ AffectedValues affectedValues) {
+ if (value.isDefinedByInstructionSatisfying(Instruction::isNewArrayEmpty)
+ && value.definition.asNewArrayEmpty().sizeIfConst() == 0) {
+ instructionIterator.previous();
+ value.replaceUsers(
+ instructionIterator.insertConstNullInstruction(code, options), affectedValues);
+ instructionIterator.next();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 1dc3ca2..1ea01a3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -46,6 +46,9 @@
this.appView = appView;
timing.begin("Register optimizers");
PrimitiveMethodOptimizer.forEachPrimitiveOptimizer(appView, this::register);
+ register(new ClassOptimizer(appView));
+ register(new ConstructorOptimizer(appView));
+ register(new MethodOptimizer(appView));
register(new ObjectMethodOptimizer(appView));
register(new ObjectsMethodOptimizer(appView));
register(new StringBuilderMethodOptimizer(appView));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/MethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/MethodOptimizer.java
new file mode 100644
index 0000000..78b0149
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/MethodOptimizer.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2023, 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.library;
+
+import com.android.tools.r8.graph.AppView;
+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.DexType;
+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.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.optimize.AffectedValues;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.Set;
+
+public class MethodOptimizer extends StatelessLibraryMethodModelCollection {
+
+ private final InternalOptions options;
+ private final DexItemFactory dexItemFactory;
+ private final DexMethod invoke;
+
+ MethodOptimizer(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.options = appView.options();
+ this.dexItemFactory = dexItemFactory;
+ invoke = dexItemFactory.methodMethods.invoke;
+ }
+
+ @Override
+ public DexType getType() {
+ return dexItemFactory.methodType;
+ }
+
+ @Override
+ @SuppressWarnings("ReferenceEquality")
+ public InstructionListIterator optimize(
+ IRCode code,
+ BasicBlockIterator blockIterator,
+ InstructionListIterator instructionIterator,
+ InvokeMethod invoke,
+ DexClassAndMethod singleTarget,
+ AffectedValues affectedValues,
+ Set<BasicBlock> blocksToRemove) {
+ DexMethod singleTargetReference = singleTarget.getReference();
+ if (singleTargetReference.isIdenticalTo(this.invoke)) {
+ EmptyVarargsUtil.replaceWithNullIfEmptyArray(
+ invoke.getArgument(2), code, instructionIterator, options, affectedValues);
+ assert instructionIterator.peekPrevious() == invoke;
+ }
+ return instructionIterator;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
index 0c471b0..2b30686 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerImpl.java
@@ -110,18 +110,20 @@
Map<DexMethod, DexMethod> vMethodRepresentative = new IdentityHashMap<>();
for (List<ProgramMethod> vMethods : componentVirtualMethods.values()) {
if (vMethods.size() > 1) {
- if (Iterables.all(vMethods, this::shouldConsiderForUnboxing)) {
+ if (Iterables.all(vMethods, this::shouldConsiderForUnboxing)
+ && Iterables.any(vMethods, m -> !m.getDefinition().isAbstract())) {
vMethods.sort(Comparator.comparing(DexClassAndMember::getReference));
ProgramMethod representative = vMethods.get(0);
for (int i = 1; i < vMethods.size(); i++) {
vMethodRepresentative.put(
vMethods.get(i).getReference(), representative.getReference());
}
+ candidateBoxingStatus.put(representative.getReference(), UNPROCESSED_CANDIDATE);
}
} else {
assert vMethods.size() == 1;
ProgramMethod candidate = vMethods.get(0);
- if (shouldConsiderForUnboxing(candidate)) {
+ if (shouldConsiderForUnboxing(candidate) && !candidate.getDefinition().isAbstract()) {
candidateBoxingStatus.put(candidate.getReference(), UNPROCESSED_CANDIDATE);
}
}
@@ -131,8 +133,7 @@
private void registerMethodUnboxingStatusIfNeeded(
ProgramMethod method, ValueBoxingStatus returnStatus, ValueBoxingStatus[] args) {
- DexMethod representative =
- virtualMethodsRepresentative.getOrDefault(method.getReference(), method.getReference());
+ DexMethod representative = representative(method.getReference());
if (args == null && (returnStatus == null || returnStatus.isNotUnboxable())) {
// Effectively NOT_UNBOXABLE, remove the candidate.
// TODO(b/307872552): Do we need to remove at the end of the wave for determinism?
@@ -153,6 +154,10 @@
}
}
+ private DexMethod representative(DexMethod method) {
+ return virtualMethodsRepresentative.getOrDefault(method, method);
+ }
+
/**
* Analysis phase: Figures out in each method if parameters, invoke, field accesses and return
* values are used in boxing operations.
@@ -260,7 +265,9 @@
if (definition.isArgument()) {
int shift = BooleanUtils.intValue(!context.getDefinition().isStatic());
return ValueBoxingStatus.with(
- new MethodArg(definition.asArgument().getIndex() - shift, context.getReference()));
+ new MethodArg(
+ definition.asArgument().getIndex() - shift,
+ representative(context.getReference())));
}
if (definition.isInvokeMethod()) {
if (boxPrimitiveMethod.isIdenticalTo(definition.asInvokeMethod().getInvokedMethod())) {
@@ -279,7 +286,8 @@
.resolveMethodLegacy(invoke.getInvokedMethod(), invoke.getInterfaceBit())
.getResolvedProgramMethod();
if (resolvedMethod != null) {
- return ValueBoxingStatus.with(new MethodRet(invoke.getInvokedMethod()));
+ return ValueBoxingStatus.with(
+ new MethodRet(representative(resolvedMethod.getReference())));
}
}
}
@@ -346,7 +354,8 @@
}
NumberUnboxerLens numberUnboxerLens =
- new NumberUnboxerTreeFixer(appView, unboxingResult).fixupTree(executorService, timing);
+ new NumberUnboxerTreeFixer(appView, unboxingResult, virtualMethodsRepresentative)
+ .fixupTree(executorService, timing);
appView.rewriteWithLens(numberUnboxerLens, executorService, timing);
new NumberUnboxerMethodReprocessingEnqueuer(appView, numberUnboxerLens)
.enqueueMethodsForReprocessing(postMethodProcessorBuilder, executorService, timing);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
index 8e7c278..e33ceb1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerLens.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.proto.RewrittenTypeInfo;
import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
@@ -82,12 +83,14 @@
ArgumentInfoCollection.Builder builder =
ArgumentInfoCollection.builder()
.setArgumentInfosSize(from.getNumberOfArguments(staticMethod));
+ int shift = BooleanUtils.intValue(!staticMethod);
for (int i = 0; i < from.getParameters().size(); i++) {
DexType fromType = from.getParameter(i);
DexType toType = to.getParameter(i);
if (!fromType.isIdenticalTo(toType)) {
builder.addArgumentInfo(
- i, RewrittenTypeInfo.builder().setOldType(fromType).setNewType(toType).build());
+ shift + i,
+ RewrittenTypeInfo.builder().setOldType(fromType).setNewType(toType).build());
}
}
RewrittenTypeInfo returnInfo =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java
index 166cccd..0239e02 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/numberunboxer/NumberUnboxerTreeFixer.java
@@ -26,16 +26,19 @@
public class NumberUnboxerTreeFixer implements ProgramClassFixer {
- private final Map<DexMethod, MethodBoxingStatusResult> unboxingResult;
private final AppView<AppInfoWithLiveness> appView;
+ private final Map<DexMethod, MethodBoxingStatusResult> unboxingResult;
+ private final Map<DexMethod, DexMethod> virtualMethodsRepresentative;
private final NumberUnboxerLens.Builder lensBuilder = NumberUnboxerLens.builder();
public NumberUnboxerTreeFixer(
AppView<AppInfoWithLiveness> appView,
- Map<DexMethod, MethodBoxingStatusResult> unboxingResult) {
- this.unboxingResult = unboxingResult;
+ Map<DexMethod, MethodBoxingStatusResult> unboxingResult,
+ Map<DexMethod, DexMethod> virtualMethodsRepresentative) {
this.appView = appView;
+ this.unboxingResult = unboxingResult;
+ this.virtualMethodsRepresentative = virtualMethodsRepresentative;
}
public NumberUnboxerLens fixupTree(ExecutorService executorService, Timing timing)
@@ -56,12 +59,16 @@
public boolean shouldReserveAsIfPinned(ProgramMethod method) {
// We don't reprocess dependencies of unchanged methods so we have to maintain them
// with the same signature.
- return !unboxingResult.containsKey(method.getReference());
+ return !unboxingResult.containsKey(representative(method.getReference()));
+ }
+
+ private DexMethod representative(DexMethod method) {
+ return virtualMethodsRepresentative.getOrDefault(method, method);
}
private DexEncodedMethod fixupEncodedMethod(
DexEncodedMethod method, MethodNamingUtility utility) {
- if (!unboxingResult.containsKey(method.getReference())) {
+ if (!unboxingResult.containsKey(representative(method.getReference()))) {
assert method
.getReference()
.isIdenticalTo(
@@ -69,7 +76,8 @@
method, method.getProto(), appView.dexItemFactory().shortType));
return method;
}
- MethodBoxingStatusResult methodBoxingStatus = unboxingResult.get(method.getReference());
+ MethodBoxingStatusResult methodBoxingStatus =
+ unboxingResult.get(representative(method.getReference()));
assert !methodBoxingStatus.isNoneUnboxable();
DexProto newProto = fixupProto(method.getProto(), methodBoxingStatus);
DexMethod newMethod =
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 2f4b4b9..f15bba1 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
@@ -126,13 +127,12 @@
}
ReservedFieldNamingState reservationState =
getOrCreateReservedFieldNamingState(frontier);
- for (DexEncodedField field : clazz.fields()) {
- DexString reservedName = strategy.getReservedName(field, clazz);
+ for (DexClassAndField field : clazz.classFields()) {
+ DexString reservedName = strategy.getReservedName(field);
if (reservedName != null) {
- reservationState.markReserved(
- reservedName, field.getReference().name, field.getReference().type);
+ reservationState.markReserved(reservedName, field);
// TODO(b/148846065): Consider lazily computing the renaming on actual lookups.
- if (reservedName != field.getReference().name) {
+ if (reservedName.isNotIdenticalTo(field.getName())) {
renaming.put(field.getReference(), reservedName);
}
}
@@ -214,16 +214,15 @@
});
}
- @SuppressWarnings("ReferenceEquality")
private void renameFieldsInUnrelatedClasspathClasses() {
if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
appView
.appInfo()
.forEachReferencedClasspathClass(
clazz -> {
- for (DexEncodedField field : clazz.fields()) {
- DexString reservedName = strategy.getReservedName(field, clazz);
- if (reservedName != null && reservedName != field.getReference().name) {
+ for (DexClassAndField field : clazz.classFields()) {
+ DexString reservedName = strategy.getReservedName(field);
+ if (reservedName != null && reservedName.isNotIdenticalTo(field.getName())) {
renaming.put(field.getReference(), reservedName);
}
}
@@ -267,8 +266,7 @@
.forEachProgramField(
field -> {
DexString newName = renameField(field, state);
- namesToBeReservedInImplementsSubclasses.markReserved(
- newName, field.getReference().name, field.getReference().type);
+ namesToBeReservedInImplementsSubclasses.markReserved(newName, field);
});
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
index e507112..6490228 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingState.java
@@ -49,7 +49,7 @@
}
public DexString getOrCreateNameFor(ProgramField field) {
- DexString reservedName = strategy.getReservedName(field.getDefinition(), field.getHolder());
+ DexString reservedName = strategy.getReservedName(field);
if (reservedName != null) {
return reservedName;
}
@@ -57,10 +57,6 @@
return getOrCreateInternalState(field.getReference()).createNewName(field);
}
- public void includeReservations(ReservedFieldNamingState reservedNames) {
- this.reservedNames.includeReservations(reservedNames);
- }
-
@Override
public InternalState createInternalState() {
return new InternalState();
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java b/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java
index 693f373..b9f8454 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNamingStateBase.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.ProguardConfiguration;
import java.util.Map;
abstract class FieldNamingStateBase<T> {
@@ -36,11 +35,10 @@
return internalStates.computeIfAbsent(internalStateKey, key -> createInternalState());
}
+ @SuppressWarnings("UnusedVariable")
private DexType getInternalStateKey(DexType type) {
- ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
- return proguardConfiguration.isOverloadAggressively()
- ? type
- : appView.dexItemFactory().voidType;
+ // Returning the given type instead of void will implement aggressive overloading.
+ return appView.dexItemFactory().voidType;
}
abstract T createInternalState();
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 4f1544e..d3bfef8 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -3,9 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
@@ -16,11 +19,12 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DisjointSets;
import com.android.tools.r8.utils.MethodJavaSignatureEquivalence;
-import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.collections.DexClassAndMethodMap;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.io.PrintStream;
import java.util.ArrayList;
@@ -110,11 +114,11 @@
this.iface = iface;
}
- DexString getReservedName(DexEncodedMethod method) {
+ DexString getReservedName(DexClassAndMethod method) {
// If an interface is kept and we are using applymapping, the renamed name for this method
// is tracked on this level.
if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
- DexString reservedName = minifierState.getReservedName(method, iface);
+ DexString reservedName = minifierState.getReservedName(method);
if (reservedName != null) {
return reservedName;
}
@@ -143,7 +147,7 @@
this.reservationTypes.add(type);
}
- void reserveName(DexString reservedName, DexEncodedMethod method) {
+ void reserveName(DexString reservedName, DexClassAndMethod method) {
forAll(
s -> {
s.reservationTypes.forEach(
@@ -154,7 +158,7 @@
});
}
- boolean isAvailable(DexString candidate, DexEncodedMethod method) {
+ boolean isAvailable(DexString candidate, DexClassAndMethod method) {
Boolean result =
forAny(
s -> {
@@ -169,14 +173,14 @@
return result == null || result;
}
- void addRenaming(DexString newName, DexEncodedMethod method) {
+ void addRenaming(DexString newName, DexClassAndMethod method) {
forAll(
s ->
s.reservationTypes.forEach(
resType -> minifierState.getNamingState(resType).addRenaming(newName, method)));
}
- <T> void forAll(Consumer<InterfaceReservationState> action) {
+ void forAll(Consumer<InterfaceReservationState> action) {
forAny(
s -> {
action.accept(s);
@@ -238,20 +242,19 @@
class InterfaceMethodGroupState implements Comparable<InterfaceMethodGroupState> {
private final Set<DexCallSite> callSites = new HashSet<>();
- private final Map<DexEncodedMethod, Set<InterfaceReservationState>> methodStates =
- new HashMap<>();
- private final List<DexEncodedMethod> callSiteCollidingMethods = new ArrayList<>();
+ private final DexClassAndMethodMap<Set<InterfaceReservationState>> methodStates =
+ DexClassAndMethodMap.create();
+ private final List<DexClassAndMethod> callSiteCollidingMethods = new ArrayList<>();
- void addState(DexEncodedMethod method, InterfaceReservationState interfaceState) {
- methodStates.computeIfAbsent(method, m -> new HashSet<>()).add(interfaceState);
+ void addState(DexClassAndMethod method, InterfaceReservationState interfaceState) {
+ methodStates.computeIfAbsent(method, ignoreKey(HashSet::new)).add(interfaceState);
}
void appendMethodGroupState(InterfaceMethodGroupState state) {
callSites.addAll(state.callSites);
callSiteCollidingMethods.addAll(state.callSiteCollidingMethods);
- for (DexEncodedMethod key : state.methodStates.keySet()) {
- methodStates.computeIfAbsent(key, k -> new HashSet<>()).addAll(state.methodStates.get(key));
- }
+ state.methodStates.forEach(
+ (key, value) -> methodStates.computeIfAbsent(key, ignoreKey(HashSet::new)).addAll(value));
}
void addCallSite(DexCallSite callSite) {
@@ -260,7 +263,6 @@
callSites.add(callSite);
}
- @SuppressWarnings("ReferenceEquality")
DexString getReservedName() {
if (methodStates.isEmpty()) {
return null;
@@ -268,13 +270,11 @@
// It is perfectly fine to have multiple reserved names inside a group. If we have an identity
// reservation, we have to prioritize that over the others, otherwise we just propose the
// first ordered reserved name since we do not allow overwriting the name.
- List<DexEncodedMethod> sortedMethods = Lists.newArrayList(methodStates.keySet());
- sortedMethods.sort((x, y) -> x.getReference().compareTo(y.getReference()));
DexString reservedName = null;
- for (DexEncodedMethod method : sortedMethods) {
+ for (DexClassAndMethod method : methodStates.getKeysSorted()) {
for (InterfaceReservationState state : methodStates.get(method)) {
DexString stateReserved = state.getReservedName(method);
- if (stateReserved == method.getName()) {
+ if (method.getName().isIdenticalTo(stateReserved)) {
return method.getName();
} else if (stateReserved != null) {
reservedName = stateReserved;
@@ -320,7 +320,7 @@
});
}
- void forEachState(BiConsumer<DexEncodedMethod, InterfaceReservationState> action) {
+ void forEachState(BiConsumer<DexClassAndMethod, InterfaceReservationState> action) {
forAnyState(
(s, i) -> {
action.accept(s, i);
@@ -328,21 +328,25 @@
});
}
- <T> T forAnyState(BiFunction<DexEncodedMethod, InterfaceReservationState, T> callback) {
- T returnValue;
- for (Map.Entry<DexEncodedMethod, Set<InterfaceReservationState>> entry :
- methodStates.entrySet()) {
- for (InterfaceReservationState state : entry.getValue()) {
- returnValue = callback.apply(entry.getKey(), state);
- if (returnValue != null) {
- return returnValue;
- }
- }
+ <T> T forAnyState(BiFunction<DexClassAndMethod, InterfaceReservationState, T> callback) {
+ TraversalContinuation<T, Void> traversalContinuation =
+ methodStates.traverse(
+ (key, value) -> {
+ for (InterfaceReservationState state : value) {
+ T returnValue = callback.apply(key, state);
+ if (returnValue != null) {
+ return TraversalContinuation.doBreak(returnValue);
+ }
+ }
+ return TraversalContinuation.doContinue();
+ });
+ if (traversalContinuation.isBreak()) {
+ return traversalContinuation.asBreak().getValue();
}
return null;
}
- boolean containsReservation(DexEncodedMethod method, DexType reservationType) {
+ boolean containsReservation(DexClassAndMethod method, DexType reservationType) {
Set<InterfaceReservationState> states = methodStates.get(method);
if (states != null) {
for (InterfaceReservationState state : states) {
@@ -361,15 +365,30 @@
}
}
+ // Replacing the use of MethodJavaSignatureEquivalence by MethodSignatureEquivalence implements
+ // aggressive overloading.
+ private static final Equivalence<DexClassAndMethod> equivalence =
+ new Equivalence<>() {
+
+ @Override
+ protected boolean doEquivalent(DexClassAndMethod method, DexClassAndMethod other) {
+ return MethodJavaSignatureEquivalence.get()
+ .equivalent(method.getReference(), other.getReference());
+ }
+
+ @Override
+ protected int doHash(DexClassAndMethod method) {
+ return MethodJavaSignatureEquivalence.get().hash(method.getReference());
+ }
+ };
+
private final AppView<AppInfoWithLiveness> appView;
private final SubtypingInfo subtypingInfo;
- private final Equivalence<DexMethod> equivalence;
- private final Equivalence<DexEncodedMethod> definitionEquivalence;
private final MethodNameMinifier.State minifierState;
/** A map from DexMethods to all the states linked to interfaces they appear in. */
- private final Map<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> globalStateMap =
- new HashMap<>();
+ private final DexClassAndMethodMap<InterfaceMethodGroupState> globalStateMap =
+ createDexClassAndMethodMap();
/** A map for caching all interface states. */
private final Map<DexType, InterfaceReservationState> interfaceStateMap = new HashMap<>();
@@ -379,25 +398,19 @@
this.appView = appView;
this.minifierState = minifierState;
this.subtypingInfo = subtypingInfo;
- this.equivalence =
- appView.options().getProguardConfiguration().isOverloadAggressively()
- ? MethodSignatureEquivalence.get()
- : MethodJavaSignatureEquivalence.get();
- this.definitionEquivalence =
- new Equivalence<>() {
- @Override
- protected boolean doEquivalent(DexEncodedMethod method, DexEncodedMethod other) {
- return equivalence.equivalent(method.getReference(), other.getReference());
- }
-
- @Override
- protected int doHash(DexEncodedMethod method) {
- return equivalence.hash(method.getReference());
- }
- };
}
- private Comparator<Wrapper<DexEncodedMethod>> getDefaultInterfaceMethodOrdering() {
+ private static <V> DexClassAndMethodMap<V> createDexClassAndMethodMap() {
+ return new DexClassAndMethodMap<>(new HashMap<>()) {
+
+ @Override
+ protected Wrapper<DexClassAndMethod> wrap(DexClassAndMethod method) {
+ return equivalence.wrap(method);
+ }
+ };
+ }
+
+ private Comparator<DexClassAndMethod> getDefaultInterfaceMethodOrdering() {
return Comparator.comparing(globalStateMap::get);
}
@@ -430,10 +443,9 @@
for (DexClass iface : interfaces) {
InterfaceReservationState inheritanceState = interfaceStateMap.get(iface.type);
assert inheritanceState != null;
- for (DexEncodedMethod method : iface.methods()) {
- Wrapper<DexEncodedMethod> key = definitionEquivalence.wrap(method);
+ for (DexClassAndMethod method : iface.classMethods()) {
globalStateMap
- .computeIfAbsent(key, k -> new InterfaceMethodGroupState())
+ .computeIfAbsent(method, ignoreKey(InterfaceMethodGroupState::new))
.addState(method, inheritanceState);
}
}
@@ -451,21 +463,21 @@
// Note that if the input does not use multi-interface lambdas unificationParent will remain
// empty.
timing.begin("Union-find");
- DisjointSets<Wrapper<DexEncodedMethod>> unification = new DisjointSets<>();
+ DisjointSets<Wrapper<DexClassAndMethod>> unification = new DisjointSets<>();
liveCallSites.forEach(
callSite -> {
- Set<Wrapper<DexEncodedMethod>> callSiteMethods = new HashSet<>();
+ Set<Wrapper<DexClassAndMethod>> callSiteMethods = new HashSet<>();
// Don't report errors, as the set of call sites is a conservative estimate, and can
// refer to interfaces which has been removed.
- Set<DexEncodedMethod> implementedMethods =
+ DexClassAndMethodSet implementedMethods =
appView.appInfo().lookupLambdaImplementedMethods(callSite, appView);
- for (DexEncodedMethod method : implementedMethods) {
- Wrapper<DexEncodedMethod> wrapped = definitionEquivalence.wrap(method);
- InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
- assert groupState != null : wrapped;
+ for (DexClassAndMethod method : implementedMethods) {
+ Wrapper<DexClassAndMethod> wrapper = equivalence.wrap(method);
+ InterfaceMethodGroupState groupState = globalStateMap.get(wrapper);
+ assert groupState != null : wrapper;
groupState.addCallSite(callSite);
- callSiteMethods.add(wrapped);
+ callSiteMethods.add(wrapper);
}
if (callSiteMethods.isEmpty()) {
return;
@@ -480,8 +492,8 @@
// name.
DexClass iface = appView.definitionFor(implementedInterfaces.get(i));
assert iface.isInterface();
- for (DexEncodedMethod implementedMethod : implementedMethods) {
- for (DexEncodedMethod virtualMethod : iface.virtualMethods()) {
+ for (DexClassAndMethod implementedMethod : implementedMethods) {
+ for (DexClassAndMethod virtualMethod : iface.virtualClassMethods()) {
boolean differentName = implementedMethod.getName() != virtualMethod.getName();
if (differentName
&& MethodJavaSignatureEquivalence.getEquivalenceIgnoreName()
@@ -489,8 +501,7 @@
implementedMethod.getReference(), virtualMethod.getReference())) {
InterfaceMethodGroupState interfaceMethodGroupState =
globalStateMap.computeIfAbsent(
- definitionEquivalence.wrap(implementedMethod),
- k -> new InterfaceMethodGroupState());
+ implementedMethod, ignoreKey(InterfaceMethodGroupState::new));
interfaceMethodGroupState.callSiteCollidingMethods.add(virtualMethod);
}
}
@@ -499,9 +510,9 @@
}
if (callSiteMethods.size() > 1) {
// Implemented interfaces have different protos. Unify them.
- Wrapper<DexEncodedMethod> mainKey = callSiteMethods.iterator().next();
- Wrapper<DexEncodedMethod> representative = unification.findOrMakeSet(mainKey);
- for (Wrapper<DexEncodedMethod> key : callSiteMethods) {
+ Wrapper<DexClassAndMethod> mainKey = callSiteMethods.iterator().next();
+ Wrapper<DexClassAndMethod> representative = unification.findOrMakeSet(mainKey);
+ for (Wrapper<DexClassAndMethod> key : callSiteMethods) {
unification.unionWithMakeSet(representative, key);
}
}
@@ -512,32 +523,32 @@
// We now have roots for all unions. Add all of the states for the groups to the method state
// for the unions to allow consistent naming across different protos.
timing.begin("States for union");
- Map<Wrapper<DexEncodedMethod>, Set<Wrapper<DexEncodedMethod>>> unions =
- unification.collectSets();
-
- for (Wrapper<DexEncodedMethod> wrapped : unions.keySet()) {
- InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
- assert groupState != null;
-
- for (Wrapper<DexEncodedMethod> groupedMethod : unions.get(wrapped)) {
- DexEncodedMethod method = groupedMethod.get();
- assert method != null;
- groupState.appendMethodGroupState(globalStateMap.get(groupedMethod));
- }
- }
+ DexClassAndMethodMap<Set<Wrapper<DexClassAndMethod>>> unions = createDexClassAndMethodMap();
+ unification.consumeSets(
+ (representative, element) ->
+ unions.computeIfAbsent(representative, ignoreKey(HashSet::new)).add(element));
+ unions.forEach(
+ (representative, elements) -> {
+ InterfaceMethodGroupState groupState = globalStateMap.get(representative);
+ assert groupState != null;
+ for (Wrapper<DexClassAndMethod> groupedMethod : elements) {
+ groupState.appendMethodGroupState(globalStateMap.get(groupedMethod));
+ }
+ });
timing.end();
timing.begin("Sort");
// Filter out the groups that is included both in the unification and in the map. We sort the
// methods by the number of dependent states, so that we use short names for method that are
// referenced in many places.
- List<Wrapper<DexEncodedMethod>> interfaceMethodGroups =
- globalStateMap.keySet().stream()
+ List<DexClassAndMethod> interfaceMethodGroups =
+ globalStateMap
+ .streamWrappedKeys()
.filter(unification::isRepresentativeOrNotPresent)
+ .map(Wrapper::get)
.sorted(
appView
- .options()
- .testing
+ .testing()
.minifier
.getInterfaceMethodOrderingOrDefault(getDefaultInterfaceMethodOrdering()))
.collect(Collectors.toList());
@@ -549,8 +560,8 @@
timing.begin("Reserve in groups");
// It is important that this entire phase is run before given new names, to ensure all
// reservations are propagated to all naming states.
- List<Wrapper<DexEncodedMethod>> nonReservedMethodGroups = new ArrayList<>();
- for (Wrapper<DexEncodedMethod> interfaceMethodGroup : interfaceMethodGroups) {
+ List<DexClassAndMethod> nonReservedMethodGroups = new ArrayList<>();
+ for (DexClassAndMethod interfaceMethodGroup : interfaceMethodGroups) {
InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
assert groupState != null;
DexString reservedName = groupState.getReservedName();
@@ -564,39 +575,43 @@
timing.end();
timing.begin("Rename in groups");
- for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) {
+ for (DexClassAndMethod interfaceMethodGroup : nonReservedMethodGroups) {
InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
assert groupState != null;
assert groupState.getReservedName() == null;
- DexString newName = assignNewName(interfaceMethodGroup.get(), groupState);
+ DexString newName = assignNewName(interfaceMethodGroup, groupState);
assert newName != null;
Set<String> loggingFilter = appView.options().extensiveInterfaceMethodMinifierLoggingFilter;
if (!loggingFilter.isEmpty()) {
- Set<DexEncodedMethod> sourceMethods = groupState.methodStates.keySet();
- if (sourceMethods.stream()
- .map(DexEncodedMethod::toSourceString)
+ if (groupState
+ .methodStates
+ .streamKeys()
+ .map(DexClassAndMethod::toSourceString)
.anyMatch(loggingFilter::contains)) {
- print(interfaceMethodGroup.get().getReference(), sourceMethods, System.out);
+ print(
+ interfaceMethodGroup.getReference(),
+ groupState.methodStates.getKeysSorted(),
+ System.out);
}
}
}
// After all naming is completed for callsites, we must ensure to rename all interface methods
// that can collide with the callsite method name.
- for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) {
+ for (DexClassAndMethod interfaceMethodGroup : nonReservedMethodGroups) {
InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
if (groupState.callSiteCollidingMethods.isEmpty()) {
continue;
}
- DexEncodedMethod key = interfaceMethodGroup.get();
- MethodNamingState<?> keyNamingState = minifierState.getNamingState(key.getHolderType());
- DexString existingRenaming = keyNamingState.newOrReservedNameFor(key);
+ MethodNamingState<?> keyNamingState =
+ minifierState.getNamingState(interfaceMethodGroup.getHolderType());
+ DexString existingRenaming = keyNamingState.newOrReservedNameFor(interfaceMethodGroup);
assert existingRenaming != null;
- for (DexEncodedMethod collidingMethod : groupState.callSiteCollidingMethods) {
+ for (DexClassAndMethod collidingMethod : groupState.callSiteCollidingMethods) {
DexString newNameInGroup = newNameInGroup(collidingMethod, keyNamingState, groupState);
minifierState.putRenaming(collidingMethod, newNameInGroup);
MethodNamingState<?> methodNamingState =
- minifierState.getNamingState(collidingMethod.getReference().holder);
+ minifierState.getNamingState(collidingMethod.getHolderType());
methodNamingState.addRenaming(newNameInGroup, collidingMethod);
keyNamingState.addRenaming(newNameInGroup, collidingMethod);
}
@@ -606,7 +621,7 @@
timing.end(); // end compute timing
}
- private DexString assignNewName(DexEncodedMethod method, InterfaceMethodGroupState groupState) {
+ private DexString assignNewName(DexClassAndMethod method, InterfaceMethodGroupState groupState) {
assert groupState.getReservedName() == null;
assert groupState.methodStates.containsKey(method);
assert groupState.containsReservation(method, method.getHolderType());
@@ -620,7 +635,7 @@
}
private DexString newNameInGroup(
- DexEncodedMethod method,
+ DexClassAndMethod method,
MethodNamingState<?> namingState,
InterfaceMethodGroupState groupState) {
// Check if the name is available in all states.
@@ -663,50 +678,52 @@
}));
}
- private boolean verifyAllCallSitesAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) {
- Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups);
+ private boolean verifyAllCallSitesAreRepresentedIn(List<DexClassAndMethod> groups) {
+ Set<Wrapper<DexClassAndMethod>> unifiedMethods = new HashSet<>(groups.size());
+ groups.forEach(group -> unifiedMethods.add(equivalence.wrap(group)));
Set<DexCallSite> unifiedSeen = new HashSet<>();
Set<DexCallSite> seen = new HashSet<>();
- for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state :
- globalStateMap.entrySet()) {
- for (DexCallSite callSite : state.getValue().callSites) {
- seen.add(callSite);
- if (unifiedMethods.contains(state.getKey())) {
- boolean added = unifiedSeen.add(callSite);
- assert added;
- }
- }
- }
+ globalStateMap.forEach(
+ (key, value) -> {
+ for (DexCallSite callSite : value.callSites) {
+ seen.add(callSite);
+ if (unifiedMethods.contains(equivalence.wrap(key))) {
+ boolean added = unifiedSeen.add(callSite);
+ assert added;
+ }
+ }
+ });
assert seen.size() == unifiedSeen.size();
assert unifiedSeen.containsAll(seen);
return true;
}
- private boolean verifyAllMethodsAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) {
- Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups);
+ private boolean verifyAllMethodsAreRepresentedIn(List<DexClassAndMethod> groups) {
+ Set<Wrapper<DexClassAndMethod>> unifiedMethods = new HashSet<>(groups.size());
+ groups.forEach(group -> unifiedMethods.add(equivalence.wrap(group)));
Set<DexEncodedMethod> unifiedSeen = Sets.newIdentityHashSet();
Set<DexEncodedMethod> seen = Sets.newIdentityHashSet();
- for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state :
- globalStateMap.entrySet()) {
- for (DexEncodedMethod method : state.getValue().methodStates.keySet()) {
- seen.add(method);
- if (unifiedMethods.contains(state.getKey())) {
- boolean added = unifiedSeen.add(method);
- assert added;
- }
- }
- }
+ globalStateMap.forEach(
+ (representative, value) ->
+ value.methodStates.forEachKey(
+ method -> {
+ seen.add(method.getDefinition());
+ if (unifiedMethods.contains(equivalence.wrap(representative))) {
+ boolean added = unifiedSeen.add(method.getDefinition());
+ assert added;
+ }
+ }));
assert seen.size() == unifiedSeen.size();
assert unifiedSeen.containsAll(seen);
return true;
}
- private void print(DexMethod method, Set<DexEncodedMethod> sourceMethods, PrintStream out) {
+ private void print(DexMethod method, List<DexClassAndMethod> sourceMethods, PrintStream out) {
out.println("-----------------------------------------------------------------------");
out.println("assignNameToInterfaceMethod(`" + method.toSourceString() + "`)");
out.println("-----------------------------------------------------------------------");
out.println("Source methods:");
- for (DexEncodedMethod sourceMethod : sourceMethods) {
+ for (DexClassAndMethod sourceMethod : sourceMethods) {
out.println(" " + sourceMethod.toSourceString());
}
out.println("States:");
diff --git a/src/main/java/com/android/tools/r8/naming/MapConsumer.java b/src/main/java/com/android/tools/r8/naming/MapConsumer.java
index 236c2b7..01c497f 100644
--- a/src/main/java/com/android/tools/r8/naming/MapConsumer.java
+++ b/src/main/java/com/android/tools/r8/naming/MapConsumer.java
@@ -15,6 +15,5 @@
void accept(
DiagnosticsHandler diagnosticsHandler,
- ProguardMapMarkerInfo makerInfo,
ClassNameMapper classNameMapper);
}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index 58a4dc6..15b2aae 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -5,8 +5,9 @@
package com.android.tools.r8.naming;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndField;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.ProgramField;
@@ -15,7 +16,7 @@
public interface MemberNamingStrategy {
DexString next(
- DexEncodedMethod method,
+ DexClassAndMethod method,
InternalNamingState internalState,
BiPredicate<DexString, DexMethod> isAvailable);
@@ -24,9 +25,13 @@
InternalNamingState internalState,
BiPredicate<DexString, ProgramField> isAvailable);
- DexString getReservedName(DexEncodedMethod method, DexClass holder);
+ DexString getReservedName(DexClassAndMethod method);
- DexString getReservedName(DexEncodedField field, DexClass holder);
+ DexString getReservedName(DexClassAndField field);
- boolean allowMemberRenaming(DexClass holder);
+ boolean allowMemberRenaming(DexClass clazz);
+
+ default boolean allowMemberRenaming(DexClassAndMember<?, ?> member) {
+ return allowMemberRenaming(member.getHolder());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 4dd3d15..fa5089d 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -8,6 +8,8 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
@@ -19,15 +21,17 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -105,9 +109,8 @@
// from the method name minifier to the interface method name minifier.
class State {
- @SuppressWarnings("ReferenceEquality")
- void putRenaming(DexEncodedMethod key, DexString newName) {
- if (newName != key.getName()) {
+ void putRenaming(DexClassAndMethod key, DexString newName) {
+ if (newName.isNotIdenticalTo(key.getName())) {
renaming.put(key.getReference(), newName);
}
}
@@ -129,8 +132,8 @@
return frontiers.getOrDefault(type, type);
}
- DexString getReservedName(DexEncodedMethod method, DexClass holder) {
- return strategy.getReservedName(method, holder);
+ DexString getReservedName(DexClassAndMethod method) {
+ return strategy.getReservedName(method);
}
}
@@ -163,14 +166,9 @@
}
private Function<DexMethod, ?> getReservationKeyTransform() {
- if (appView.options().getProguardConfiguration().isOverloadAggressively()
- && appView.options().isGeneratingClassFiles()) {
- // Use the full proto as key, hence reuse names based on full signature.
- return method -> method.proto;
- } else {
- // Only use the parameters as key, hence do not reuse names on return type.
- return method -> method.proto.parameters;
- }
+ // Only use the parameters as key, hence do not reuse names on return type. Returning the full
+ // proto here implements aggressive overloading.
+ return DexMethod::getParameters;
}
private Function<DexMethod, ?> getNamingKeyTransform() {
@@ -244,23 +242,27 @@
.getOrDefault(clazz.superType, rootNamingState)
.createChild(reservationState));
if (strategy.allowMemberRenaming(clazz)) {
- for (DexEncodedMethod method : clazz.allMethodsSorted()) {
- assignNameToMethod(clazz, method, namingState);
+ List<DexClassAndMethod> allMethodsSorted =
+ ListUtils.sort(
+ clazz.classMethods(),
+ Comparator.comparing(DexClassAndMember::getReference),
+ clazz.getMethodCollection().size());
+ for (DexClassAndMethod method : allMethodsSorted) {
+ assignNameToMethod(method, namingState);
}
}
});
}
- @SuppressWarnings("ReferenceEquality")
private void renameMethodsInUnrelatedClasspathClasses() {
if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
appView
.appInfo()
.forEachReferencedClasspathClass(
clazz -> {
- for (DexEncodedMethod method : clazz.methods()) {
- DexString reservedName = strategy.getReservedName(method, clazz);
- if (reservedName != null && reservedName != method.getReference().name) {
+ for (DexClassAndMethod method : clazz.classMethods()) {
+ DexString reservedName = strategy.getReservedName(method);
+ if (reservedName != null && reservedName.isNotIdenticalTo(method.getName())) {
renaming.put(method.getReference(), reservedName);
}
}
@@ -268,20 +270,18 @@
}
}
- @SuppressWarnings("ReferenceEquality")
- private void assignNameToMethod(
- DexClass holder, DexEncodedMethod method, MethodNamingState<?> state) {
- if (method.isInitializer()) {
+ private void assignNameToMethod(DexClassAndMethod method, MethodNamingState<?> state) {
+ if (method.getDefinition().isInitializer()) {
return;
}
// The strategy may have an explicit naming for this member which we query first. It may be that
// the strategy will return the identity name, for which we have to look into a previous
// renaming tracked by the state.
- DexString newName = strategy.getReservedName(method, holder);
- if (newName == null || newName == method.getName()) {
+ DexString newName = strategy.getReservedName(method);
+ if (newName == null || newName.isIdenticalTo(method.getName())) {
newName = state.newOrReservedNameFor(method);
}
- if (method.getName() != newName) {
+ if (newName.isNotIdenticalTo(method.getName())) {
renaming.put(method.getReference(), newName);
}
state.addRenaming(newName, method);
@@ -335,20 +335,22 @@
// have to do byte-code rewriting against a mapping file to observe the issue. Doing that they
// may as well just adjust the keep rules to keep the targets of bridges.
// See b/290711987 for an actual issue regarding this.
- Set<DexEncodedMethod> bridgeMethodCandidates = Sets.newIdentityHashSet();
- Iterable<DexEncodedMethod> methods = shuffleMethods(holder.methods(), appView.options());
- for (DexEncodedMethod method : methods) {
- DexString reservedName = strategy.getReservedName(method, holder);
+ DexClassAndMethodSet bridgeMethodCandidates = DexClassAndMethodSet.create();
+ Iterable<DexClassAndMethod> methods =
+ shuffleMethods(holder.classMethods(), appView.options());
+ for (DexClassAndMethod method : methods) {
+ DexString reservedName = strategy.getReservedName(method);
if (reservedName != null) {
state.reserveName(reservedName, method);
- } else if (appView.options().isGeneratingClassFiles() && method.isSyntheticBridgeMethod()) {
+ } else if (appView.options().isGeneratingClassFiles()
+ && method.getDefinition().isSyntheticBridgeMethod()) {
bridgeMethodCandidates.add(method);
}
}
Map<DexString, Set<Integer>> methodNamesToReserve =
computeBridgesThatAreReserved(holder, bridgeMethodCandidates);
if (!methodNamesToReserve.isEmpty()) {
- for (DexEncodedMethod method : methods) {
+ for (DexClassAndMethod method : methods) {
if (methodNamesToReserve
.getOrDefault(method.getName(), Collections.emptySet())
.contains(method.getProto().getArity())) {
@@ -360,7 +362,7 @@
}
private Map<DexString, Set<Integer>> computeBridgesThatAreReserved(
- DexClass holder, Set<DexEncodedMethod> methods) {
+ DexClass holder, DexClassAndMethodSet methods) {
if (methods.isEmpty()) {
return Collections.emptyMap();
}
@@ -495,8 +497,8 @@
// Shuffles the given methods if assertions are enabled and deterministic debugging is disabled.
// Used to ensure that the generated output is deterministic.
- private static Iterable<DexEncodedMethod> shuffleMethods(
- Iterable<DexEncodedMethod> methods, InternalOptions options) {
- return options.testing.irOrdering.order(methods);
+ private static Iterable<DexClassAndMethod> shuffleMethods(
+ Iterable<DexClassAndMethod> methods, InternalOptions options) {
+ return options.testing.irOrdering.orderClassMethods(methods);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index 922c062..7a38768 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -3,7 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.naming.MethodNamingState.InternalNewNameState;
@@ -45,12 +45,12 @@
this, this.keyTransform, this.namingStrategy, frontierReservationState);
}
- DexString newOrReservedNameFor(DexEncodedMethod method) {
+ DexString newOrReservedNameFor(DexClassAndMethod method) {
return newOrReservedNameFor(method, this::isAvailable);
}
DexString newOrReservedNameFor(
- DexEncodedMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
+ DexClassAndMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
DexString newName = getAssignedName(method.getReference());
if (newName != null) {
return newName;
@@ -67,14 +67,14 @@
return nextName(method, isAvailable);
}
- DexString nextName(DexEncodedMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
+ DexString nextName(DexClassAndMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
InternalNewNameState internalState = getOrCreateInternalState(method.getReference());
DexString newName = namingStrategy.next(method, internalState, isAvailable);
assert newName != null;
return newName;
}
- void addRenaming(DexString newName, DexEncodedMethod method) {
+ void addRenaming(DexString newName, DexClassAndMethod method) {
InternalNewNameState internalState = getOrCreateInternalState(method.getReference());
internalState.addRenaming(newName, method.getReference());
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
index 61aed28..68d0378 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
@@ -4,7 +4,7 @@
package com.android.tools.r8.naming;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.naming.MethodReservationState.InternalReservationState;
@@ -37,7 +37,7 @@
return new MethodReservationState<>(this, this.keyTransform);
}
- void reserveName(DexString reservedName, DexEncodedMethod method) {
+ void reserveName(DexString reservedName, DexClassAndMethod method) {
try {
getOrCreateInternalState(method.getReference()).reserveName(method, reservedName);
} catch (AssertionError err) {
@@ -92,7 +92,7 @@
return originalToReservedNames.get(MethodSignatureEquivalence.get().wrap(method));
}
- void reserveName(DexEncodedMethod method, DexString name) {
+ void reserveName(DexClassAndMethod method, DexString name) {
if (reservedNames == null) {
assert originalToReservedNames == null;
originalToReservedNames = new HashMap<>();
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 96275ab..6c08cef 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -3,19 +3,22 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
import static com.android.tools.r8.utils.SymbolGenerationUtils.RESERVED_NAMES;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexClassAndField;
+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.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramField;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
@@ -206,8 +209,9 @@
@Override
public DexString reservedDescriptor(DexType type) {
- if (!appView.appInfo().isMinificationAllowed(type)) {
- return type.descriptor;
+ DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+ if (clazz == null || !appView.getKeepInfo(clazz).isMinificationAllowed(appView.options())) {
+ return type.getDescriptor();
}
return null;
}
@@ -277,10 +281,14 @@
@Override
public DexString next(
- DexEncodedMethod method,
+ DexClassAndMethod method,
InternalNamingState internalState,
BiPredicate<DexString, DexMethod> isAvailable) {
- assert checkAllowMemberRenaming(method.getHolderType());
+ if (!method.isProgramMethod()) {
+ assert isAvailable.test(method.getName(), method.getReference());
+ return method.getName();
+ }
+ assert allowMemberRenaming(method);
DexString candidate;
do {
candidate = getNextName(internalState);
@@ -293,7 +301,7 @@
ProgramField field,
InternalNamingState internalState,
BiPredicate<DexString, ProgramField> isAvailable) {
- assert checkAllowMemberRenaming(field.getHolderType());
+ assert allowMemberRenaming(field);
DexString candidate;
do {
candidate = getNextName(internalState);
@@ -306,40 +314,39 @@
}
@Override
- public DexString getReservedName(DexEncodedMethod method, DexClass holder) {
- if (!allowMemberRenaming(holder)
- || holder.accessFlags.isAnnotation()
- || method.accessFlags.isConstructor()
- || !appView.appInfo().isMinificationAllowed(method)) {
- return method.getReference().name;
+ public DexString getReservedName(DexClassAndMethod method) {
+ if (!allowMemberRenaming(method)) {
+ return method.getName();
+ }
+ assert method.isProgramMethod();
+ ProgramMethod programMethod = method.asProgramMethod();
+ if (method.getHolder().isAnnotation()
+ || method.getAccessFlags().isConstructor()
+ || !appView.getKeepInfo(programMethod).isMinificationAllowed(appView.options())) {
+ return method.getName();
}
if (desugaredLibraryRenaming
- && method.isLibraryMethodOverride().isTrue()
- && appView.typeRewriter.hasRewrittenTypeInSignature(
- method.getReference().proto, appView)) {
+ && method.getDefinition().isLibraryMethodOverride().isTrue()
+ && appView.typeRewriter.hasRewrittenTypeInSignature(method.getProto(), appView)) {
// With desugared library, call-backs names are reserved here.
- return method.getReference().name;
+ return method.getName();
}
return null;
}
@Override
- public DexString getReservedName(DexEncodedField field, DexClass holder) {
- if (holder.isLibraryClass() || !appView.appInfo().isMinificationAllowed(field)) {
- return field.getReference().name;
+ public DexString getReservedName(DexClassAndField field) {
+ ProgramField programField = field.asProgramField();
+ if (programField == null
+ || !appView.getKeepInfo(programField).isMinificationAllowed(appView.options())) {
+ return field.getName();
}
return null;
}
@Override
- public boolean allowMemberRenaming(DexClass holder) {
- return holder.isProgramClass();
- }
-
- public boolean checkAllowMemberRenaming(DexType holder) {
- DexClass clazz = appView.definitionFor(holder);
- assert clazz != null && allowMemberRenaming(clazz);
- return true;
+ public boolean allowMemberRenaming(DexClass clazz) {
+ return clazz.isProgramClass();
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index ed4bbbb..3e551cf 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
+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.DexField;
@@ -20,6 +21,7 @@
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.Set;
@@ -59,7 +61,7 @@
return callSite.methodName;
}
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- Set<DexEncodedMethod> lambdaImplementedMethods =
+ DexClassAndMethodSet lambdaImplementedMethods =
appViewWithLiveness.appInfo().lookupLambdaImplementedMethods(callSite, appViewWithLiveness);
if (lambdaImplementedMethods.isEmpty()) {
return callSite.methodName;
@@ -70,7 +72,7 @@
lookupMethod(lambdaImplementedMethodReference, appView.dexItemFactory()).getName();
// Verify that all lambda implemented methods are renamed consistently.
assert lambdaImplementedMethods.stream()
- .map(DexEncodedMethod::getReference)
+ .map(DexClassAndMethod::getReference)
.map(reference -> lookupMethod(reference, appView.dexItemFactory()))
.map(DexMethod::getName)
.allMatch(name -> name == renamedMethodName);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMarkerInfo.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMarkerInfo.java
index 8394ecf..7084d2d 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMarkerInfo.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMarkerInfo.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.Version;
import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.VersionProperties;
import java.util.ArrayList;
import java.util.List;
@@ -63,10 +62,6 @@
return preamble;
}
- public String serializeToString() {
- return StringUtils.unixLines(toPreamble());
- }
-
public static Builder builder() {
return new Builder();
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 588666a..ddb57d6 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.naming;
import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.defaultAsMethodOfCompanionClass;
import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.getInterfaceClassType;
import static com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringSyntheticHelper.isCompanionClassType;
@@ -13,12 +14,15 @@
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.DexDefinition;
+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.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -187,10 +191,9 @@
memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
} else {
// We have to ensure we do not rename to an existing member, that cannot be renamed.
- if (clazz == null || !appView.options().isMinifying()) {
- notMappedReferences.add(type);
- } else if (appView.options().isMinifying()
- && !appView.appInfo().isMinificationAllowed(type)) {
+ DexProgramClass programClass = asProgramClassOrNull(clazz);
+ if (programClass == null
+ || !appView.getKeepInfo(programClass).isMinificationAllowed(appView.options())) {
notMappedReferences.add(type);
}
}
@@ -400,7 +403,9 @@
public DexString next(
DexType type, char[] packagePrefix, InternalNamingState state, Predicate<String> isUsed) {
assert !mappings.containsKey(type);
- assert appView.appInfo().isMinificationAllowed(type);
+ assert appView
+ .getKeepInfo(appView.definitionFor(type).asProgramClass())
+ .isMinificationAllowed(appView.options());
return super.next(
type,
packagePrefix,
@@ -420,19 +425,21 @@
// members that can be reserved differently in the hierarchy.
DexClass clazz = appView.appInfo().definitionForWithoutExistenceAssert(type);
if (clazz == null) {
- return type.descriptor;
- }
- if (clazz.isNotProgramClass() && mappings.containsKey(type)) {
- return mappings.get(type);
+ return type.getDescriptor();
}
if (clazz.isProgramClass()) {
- if (appView.appInfo().isMinificationAllowed(clazz.asProgramClass())) {
+ DexProgramClass programClass = clazz.asProgramClass();
+ if (appView.getKeepInfo(programClass).isMinificationAllowed(appView.options())) {
return mappings.get(type);
}
// TODO(b/136694827): Report a warning here if in the mapping since the user may find this
// non intuitive.
+ } else {
+ if (mappings.containsKey(type)) {
+ return mappings.get(type);
+ }
}
- return type.descriptor;
+ return type.getDescriptor();
}
@Override
@@ -456,13 +463,11 @@
@Override
@SuppressWarnings("ReferenceEquality")
public DexString next(
- DexEncodedMethod method,
+ DexClassAndMethod method,
InternalNamingState internalState,
BiPredicate<DexString, DexMethod> isAvailable) {
DexMethod reference = method.getReference();
- DexClass holder = appView.definitionForHolder(reference);
- assert holder != null;
- DexString reservedName = getReservedName(method, reference.name, holder);
+ DexString reservedName = getReservedName(method, reference.getName());
DexString nextName;
if (reservedName != null) {
if (!isAvailable.test(reservedName, reference)) {
@@ -471,11 +476,14 @@
nextName = reservedName;
} else {
assert !mappedNames.containsKey(reference);
- assert appView.appInfo().isMinificationAllowed(method);
+ assert !method.isProgramMethod()
+ || appView
+ .getKeepInfo(method.asProgramMethod())
+ .isMinificationAllowed(appView.options());
nextName = super.next(method, internalState, isAvailable);
}
- assert nextName == reference.name || !method.isInitializer();
- assert nextName == reference.name || !holder.isAnnotation();
+ assert nextName.isIdenticalTo(reference.getName()) || !method.getDefinition().isInitializer();
+ assert nextName.isIdenticalTo(reference.getName()) || !method.getHolder().isAnnotation();
return nextName;
}
@@ -485,8 +493,7 @@
InternalNamingState internalState,
BiPredicate<DexString, ProgramField> isAvailable) {
DexField reference = field.getReference();
- DexString reservedName =
- getReservedName(field.getDefinition(), reference.name, field.getHolder());
+ DexString reservedName = getReservedName(field, reference.getName());
if (reservedName != null) {
if (!isAvailable.test(reservedName, field)) {
reportReservationError(reference, reservedName);
@@ -494,35 +501,34 @@
return reservedName;
}
assert !mappedNames.containsKey(reference);
- assert appView.appInfo().isMinificationAllowed(field);
+ assert appView.getKeepInfo(field).isMinificationAllowed(appView.options());
return super.next(field, internalState, isAvailable);
}
@Override
- public DexString getReservedName(DexEncodedMethod method, DexClass holder) {
- return getReservedName(method, method.getReference().name, holder);
+ public DexString getReservedName(DexClassAndMethod method) {
+ return getReservedName(method, method.getName());
}
@Override
- public DexString getReservedName(DexEncodedField field, DexClass holder) {
- return getReservedName(field, field.getReference().name, holder);
+ public DexString getReservedName(DexClassAndField field) {
+ return getReservedName(field, field.getName());
}
- private DexString getReservedName(DexDefinition definition, DexString name, DexClass holder) {
- assert definition.isDexEncodedMethod() || definition.isDexEncodedField();
+ private DexString getReservedName(DexClassAndMember<?, ?> definition, DexString name) {
// Always consult the mapping for renamed members that are not on program path.
DexReference reference = definition.getReference();
- if (holder.isNotProgramClass()) {
+ if (definition.getHolder().isNotProgramClass()) {
if (mappedNames.containsKey(reference)) {
return factory.createString(mappedNames.get(reference).getRenamedName());
}
return name;
}
- assert holder.isProgramClass();
+ assert definition.isProgramMember();
DexString reservedName =
- definition.isDexEncodedMethod()
- ? super.getReservedName(definition.asDexEncodedMethod(), holder)
- : super.getReservedName(definition.asDexEncodedField(), holder);
+ definition.isMethod()
+ ? super.getReservedName(definition.asMethod())
+ : super.getReservedName(definition.asField());
if (reservedName != null) {
return reservedName;
}
@@ -533,7 +539,7 @@
}
@Override
- public boolean allowMemberRenaming(DexClass holder) {
+ public boolean allowMemberRenaming(DexClass clazz) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 559d7f0..7d60962 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -652,6 +652,24 @@
// Parsing of components
+ private static boolean isAllowedIdentifierStart(int codePoint) {
+ if (IdentifierUtils.isDexIdentifierStart(codePoint)) {
+ return true;
+ }
+ // Proguard sometimes outputs a ? as a method name. We have tools (dexsplitter) that depends
+ // on being able to map class names back to the original, but does not care if methods are
+ // correctly mapped. Using this on proguard output for anything else might not give correct
+ // remappings.
+ if (IdentifierUtils.isQuestionMark(codePoint)) {
+ return true;
+ }
+ // Some mapping files contain entries starting with a '.', allow those for compatibility.
+ if (codePoint == '.') {
+ return true;
+ }
+ return false;
+ }
+
private void skipIdentifier(boolean allowInit) {
boolean isInit = false;
if (allowInit && peekChar(0) == '<') {
@@ -659,12 +677,7 @@
nextChar();
isInit = true;
}
- // Proguard sometimes outputs a ? as a method name. We have tools (dexsplitter) that depends
- // on being able to map class names back to the original, but does not care if methods are
- // correctly mapped. Using this on proguard output for anything else might not give correct
- // remappings.
- if (!IdentifierUtils.isDexIdentifierStart(peekCodePoint())
- && !IdentifierUtils.isQuestionMark(peekCodePoint())) {
+ if (!isAllowedIdentifierStart(peekCodePoint())) {
throw new ParseException("Identifier expected");
}
nextCodePoint();
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java b/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java
index 71b60d9..6f2091c 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapStringConsumer.java
@@ -26,10 +26,8 @@
@Override
public void accept(
DiagnosticsHandler diagnosticsHandler,
- ProguardMapMarkerInfo markerInfo,
ClassNameMapper classNameMapper) {
this.diagnosticsHandler = diagnosticsHandler;
- accept(markerInfo.serializeToString());
accept(StringUtils.unixLines(classNameMapper.getPreamble()));
classNameMapper.write(this);
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
index 0637406..ed3cc24 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapSupplier.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.utils.ChainableStringConsumer;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
@@ -66,16 +67,20 @@
public ProguardMapId writeProguardMap() {
ProguardMapId proguardMapId = computeProguardMapId();
- consumer.accept(
- reporter,
+ ProguardMapMarkerInfo markerInfo =
ProguardMapMarkerInfo.builder()
.setCompilerName(compiler.name())
.setProguardMapId(proguardMapId)
.setGeneratingDex(options.isGeneratingDex())
.setApiLevel(options.getMinApiLevel())
.setMapVersion(options.getMapFileVersion())
- .build(),
- classNameMapper);
+ .build();
+
+ // Set or compose the marker in the preamble information.
+ classNameMapper.setPreamble(
+ ListUtils.concat(markerInfo.toPreamble(), classNameMapper.getPreamble()));
+
+ consumer.accept(reporter, classNameMapper);
ExceptionUtils.withConsumeResourceHandler(reporter, this.consumer::finished);
return proguardMapId;
}
diff --git a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
index 2b66d18..fe4799b 100644
--- a/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/ReservedFieldNamingState.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.ReservedFieldNamingState.InternalState;
@@ -43,8 +44,8 @@
return internalState == null ? null : internalState.getReservedByName(name);
}
- void markReserved(DexString name, DexString originalName, DexType type) {
- getOrCreateInternalState(type).markReserved(name, originalName);
+ void markReserved(DexString name, DexClassAndField field) {
+ getOrCreateInternalState(field.getType()).markReserved(name, field.getName());
}
void includeReservations(ReservedFieldNamingState reservedNames) {
diff --git a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java
index e07407b..50c7bf7 100644
--- a/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java
+++ b/src/main/java/com/android/tools/r8/optimize/accessmodification/AccessModifierTraversal.java
@@ -9,14 +9,12 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.optimize.argumentpropagation.utils.DepthFirstTopDownClassHierarchyTraversal;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepClassInfo;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.collections.DexMethodSignatureMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
@@ -31,7 +29,7 @@
private final AccessModifier accessModifier;
private final AccessModifierNamingState namingState;
- private final Map<DexType, TraversalState> states = new IdentityHashMap<>();
+ private final Map<DexProgramClass, TraversalState> states = new IdentityHashMap<>();
AccessModifierTraversal(
AppView<AppInfoWithLiveness> appView,
@@ -57,29 +55,13 @@
// TODO(b/279126633): Store a top down traversal state for the current class, which contains the
// protected and public method signatures when traversing downwards to enable publicizing of
// package private methods with illegal overrides.
- states.put(clazz.getType(), TopDownTraversalState.empty());
+ states.put(clazz, TopDownTraversalState.empty());
}
/** Called during backtracking when all subclasses of {@param clazz} have been processed. */
@Override
public void prune(DexProgramClass clazz) {
- // Remove the traversal state since all subclasses have now been processed.
- states.remove(clazz.getType());
-
- // Remove and join the bottom up traversal states of the subclasses.
- KeepClassInfo keepInfo = appView.getKeepInfo(clazz);
- InternalOptions options = appView.options();
- BottomUpTraversalState state =
- new BottomUpTraversalState(
- !keepInfo.isMinificationAllowed(options) && !keepInfo.isShrinkingAllowed(options));
- forEachSubClass(
- clazz,
- subclass -> {
- BottomUpTraversalState subState =
- MapUtils.removeOrDefault(states, subclass.getType(), BottomUpTraversalState.empty())
- .asBottomUpTraversalState();
- state.add(subState);
- });
+ BottomUpTraversalState state = getOrCreateBottomUpTraversalState(clazz);
// Apply access modification to the class and its members.
accessModifier.processClass(clazz, namingState, state);
@@ -88,19 +70,48 @@
clazz.forEachProgramVirtualMethod(state::addMethod);
// Store the bottom up traversal state for the current class.
- if (state.isEmpty()) {
- states.remove(clazz.getType());
- } else {
- states.put(clazz.getType(), state);
+ if (!state.isEmpty()) {
+ immediateSubtypingInfo.forEachImmediateProgramSuperClass(
+ clazz,
+ superClass -> {
+ BottomUpTraversalState superState = getOrCreateBottomUpTraversalState(superClass);
+ superState.add(state);
+ });
}
+
+ // Done processing the current class and all subclasses.
+ states.remove(clazz);
}
- abstract static class TraversalState {
+ private BottomUpTraversalState getOrCreateBottomUpTraversalState(DexProgramClass clazz) {
+ TraversalState traversalState = states.get(clazz);
+ if (traversalState == null || traversalState.isTopDownTraversalState()) {
+ KeepClassInfo keepInfo = appView.getKeepInfo(clazz);
+ InternalOptions options = appView.options();
+ BottomUpTraversalState newState =
+ new BottomUpTraversalState(
+ !keepInfo.isMinificationAllowed(options) && !keepInfo.isShrinkingAllowed(options));
+ states.put(clazz, newState);
+ return newState;
+ }
+ assert traversalState.isBottomUpTraversalState();
+ return traversalState.asBottomUpTraversalState();
+ }
+
+ private abstract static class TraversalState {
+
+ boolean isBottomUpTraversalState() {
+ return false;
+ }
BottomUpTraversalState asBottomUpTraversalState() {
return null;
}
+ boolean isTopDownTraversalState() {
+ return false;
+ }
+
TopDownTraversalState asTopDownTraversalState() {
return null;
}
@@ -108,7 +119,7 @@
// TODO(b/279126633): Collect the protected and public method signatures when traversing downwards
// to enable publicizing of package private methods with illegal overrides.
- static class TopDownTraversalState extends TraversalState {
+ private static class TopDownTraversalState extends TraversalState {
private static final TopDownTraversalState EMPTY = new TopDownTraversalState();
@@ -117,12 +128,13 @@
}
@Override
- TopDownTraversalState asTopDownTraversalState() {
- return this;
+ boolean isTopDownTraversalState() {
+ return true;
}
- boolean isEmpty() {
- return true;
+ @Override
+ TopDownTraversalState asTopDownTraversalState() {
+ return this;
}
}
@@ -136,20 +148,29 @@
// The set of non-private virtual methods below the current class.
DexMethodSignatureMap<Set<String>> nonPrivateVirtualMethods;
- BottomUpTraversalState(boolean isKept) {
+ private BottomUpTraversalState(boolean isKept) {
this(DexMethodSignatureMap.create());
this.isKeptOrHasKeptSubclass = isKept;
}
- BottomUpTraversalState(DexMethodSignatureMap<Set<String>> packagePrivateMethods) {
+ private BottomUpTraversalState(DexMethodSignatureMap<Set<String>> packagePrivateMethods) {
this.nonPrivateVirtualMethods = packagePrivateMethods;
}
+ static BottomUpTraversalState asBottomUpTraversalStateOrNull(TraversalState traversalState) {
+ return (BottomUpTraversalState) traversalState;
+ }
+
static BottomUpTraversalState empty() {
return EMPTY;
}
@Override
+ boolean isBottomUpTraversalState() {
+ return true;
+ }
+
+ @Override
BottomUpTraversalState asBottomUpTraversalState() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
index 4b4b611..7503fa5 100644
--- a/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
+++ b/src/main/java/com/android/tools/r8/optimize/bridgehoisting/BridgeHoisting.java
@@ -114,7 +114,7 @@
assert false;
}
});
- methodAccessInfoCollectionModifier.commit(appView);
+ methodAccessInfoCollection.verify(appView);
}
}
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 e222e20..6fb0c2f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -42,7 +42,6 @@
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
-import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
@@ -71,6 +70,7 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.Visibility;
import com.android.tools.r8.utils.WorkList;
+import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.collections.ThrowingSet;
import com.android.tools.r8.utils.structural.Ordered;
@@ -764,14 +764,14 @@
* @return Methods implemented by the lambda expression that created the {@code callSite}.
*/
@SuppressWarnings("ReferenceEquality")
- public Set<DexEncodedMethod> lookupLambdaImplementedMethods(
+ public DexClassAndMethodSet lookupLambdaImplementedMethods(
DexCallSite callSite, AppView<AppInfoWithLiveness> appView) {
assert checkIfObsolete();
List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, appView);
if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
- return Collections.emptySet();
+ return DexClassAndMethodSet.empty();
}
- Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+ DexClassAndMethodSet result = DexClassAndMethodSet.create();
Deque<DexType> worklist = new ArrayDeque<>(callSiteInterfaces);
Set<DexType> visited = Sets.newIdentityHashSet();
while (!worklist.isEmpty()) {
@@ -794,8 +794,9 @@
continue;
}
assert clazz.isInterface();
- for (DexEncodedMethod method : clazz.virtualMethods()) {
- if (method.getReference().name == callSite.methodName && method.accessFlags.isAbstract()) {
+ for (DexClassAndMethod method : clazz.virtualClassMethods()) {
+ if (method.getName().isIdenticalTo(callSite.methodName)
+ && method.getAccessFlags().isAbstract()) {
result.add(method);
}
}
@@ -1009,30 +1010,6 @@
return this;
}
- @Deprecated
- public boolean isMinificationAllowed(DexProgramClass clazz) {
- return options().isMinificationEnabled()
- && keepInfo.getInfo(clazz).isMinificationAllowed(options());
- }
-
- @Deprecated
- public boolean isMinificationAllowed(ProgramDefinition definition) {
- return options().isMinificationEnabled()
- && keepInfo.getInfo(definition).isMinificationAllowed(options());
- }
-
- @Deprecated
- public boolean isMinificationAllowed(DexDefinition definition) {
- return options().isMinificationEnabled()
- && keepInfo.getInfo(definition, this).isMinificationAllowed(options());
- }
-
- @Deprecated
- public boolean isMinificationAllowed(DexType reference) {
- return options().isMinificationEnabled()
- && keepInfo.getClassInfo(reference, this).isMinificationAllowed(options());
- }
-
public boolean isRepackagingAllowed(DexProgramClass clazz, AppView<?> appView) {
if (!keepInfo.getInfo(clazz).isRepackagingAllowed(options())) {
return false;
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 4cdfc22..63e6bbb 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5177,7 +5177,9 @@
int parametersSize =
newArrayEmpty != null
? newArrayEmpty.sizeIfConst()
- : newArrayFilled != null ? newArrayFilled.size() : -1;
+ : newArrayFilled != null
+ ? newArrayFilled.size()
+ : parametersValue.isAlwaysNull(appView) ? 0 : -1;
if (parametersSize < 0) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 1410247..933cc33 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -328,7 +328,9 @@
DexItemFactory dexItemFactory = appView.dexItemFactory();
ProguardIfRule materializedRule = rule.materialize(dexItemFactory, preconditions);
- if (enqueuer.getMode().isInitialTreeShaking() && !rule.isUsed()) {
+ if (enqueuer.getMode().isInitialTreeShaking()
+ && !rule.isUsed()
+ && !rule.isTrivalAllClassMatch()) {
// We need to abort class inlining of classes that could be matched by the condition of this
// -if rule.
ClassInlineRule neverClassInlineRuleForCondition =
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 cdf0264..4c35b96 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAccessFlags.java
@@ -239,6 +239,10 @@
return this.flags == ((ProguardAccessFlags) obj).flags;
}
+ public boolean isDefaultFlags() {
+ return flags == 0;
+ }
+
@Override
public int hashCode() {
return flags;
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 d7e4ff2..98bb147 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -609,10 +609,6 @@
return rules;
}
- public boolean isOverloadAggressively() {
- return false;
- }
-
public List<String> getObfuscationDictionary() {
return obfuscationDictionary;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 6099834..01c9118 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.BooleanBox;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
@@ -60,6 +61,23 @@
memberRules);
}
+ public boolean isTrivalAllClassMatch() {
+ BooleanBox booleanBox = new BooleanBox(true);
+ getClassNames()
+ .forEachTypeMatcher(
+ unused -> booleanBox.set(false),
+ proguardTypeMatcher -> !proguardTypeMatcher.isMatchAnyClassPattern());
+ return booleanBox.get()
+ && getClassAnnotations().isEmpty()
+ && getClassAccessFlags().isDefaultFlags()
+ && getNegatedClassAccessFlags().isDefaultFlags()
+ && !getClassTypeNegated()
+ && getClassType() == ProguardClassType.CLASS
+ && getInheritanceAnnotations().isEmpty()
+ && getInheritanceClassName() == null
+ && getMemberRules().isEmpty();
+ }
+
public boolean isUsed() {
return used;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 8e19b25..79b6f5f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.List;
import java.util.Map;
@@ -195,7 +194,7 @@
? null
: getInheritanceClassName().materialize(dexItemFactory),
getInheritanceIsExtends(),
- ImmutableList.of(),
+ getMemberRules(),
ClassInlineRule.Type.NEVER);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index a8233bf..f9aef9b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -43,6 +43,10 @@
// Evaluates this matcher on the given type.
public abstract boolean matches(DexType type);
+ public boolean isMatchAnyClassPattern() {
+ return false;
+ }
+
// Evaluates this matcher on the given type, and on all types that have been merged into the given
// type, if any.
public final boolean matches(DexType type, AppView<?> appView) {
@@ -167,6 +171,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return true;
+ }
+
+ @Override
public boolean matches(DexType type) {
wildcard.setCaptured(type.toSourceString());
return true;
@@ -249,6 +258,11 @@
}
@Override
+ public boolean isMatchAnyClassPattern() {
+ return true;
+ }
+
+ @Override
public boolean matches(DexType type) {
if (type.isClassType()) {
wildcard.setCaptured(type.toSourceString());
diff --git a/src/main/java/com/android/tools/r8/utils/DexClassAndMethodEquivalence.java b/src/main/java/com/android/tools/r8/utils/DexClassAndMethodEquivalence.java
new file mode 100644
index 0000000..acb221d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DexClassAndMethodEquivalence.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.google.common.base.Equivalence;
+
+public class DexClassAndMethodEquivalence extends Equivalence<DexClassAndMethod> {
+
+ private static final DexClassAndMethodEquivalence INSTANCE = new DexClassAndMethodEquivalence();
+
+ private DexClassAndMethodEquivalence() {}
+
+ public static DexClassAndMethodEquivalence get() {
+ return INSTANCE;
+ }
+
+ @Override
+ protected boolean doEquivalent(DexClassAndMethod method, DexClassAndMethod other) {
+ return method.getDefinition() == other.getDefinition();
+ }
+
+ @Override
+ protected int doHash(DexClassAndMethod method) {
+ return method.getReference().hashCode();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DisjointSets.java b/src/main/java/com/android/tools/r8/utils/DisjointSets.java
index d8e42b9..bc5e129 100644
--- a/src/main/java/com/android/tools/r8/utils/DisjointSets.java
+++ b/src/main/java/com/android/tools/r8/utils/DisjointSets.java
@@ -4,10 +4,13 @@
package com.android.tools.r8.utils;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.function.BiConsumer;
/**
* Disjoint sets of instances of type T. Each of the sets will be represented by one of the
@@ -134,12 +137,18 @@
/** Returns the sets currently represented. */
public Map<T, Set<T>> collectSets() {
Map<T, Set<T>> unification = new HashMap<>();
+ consumeSets(
+ (representative, element) ->
+ unification.computeIfAbsent(representative, ignoreKey(HashSet::new)).add(element));
+ return unification;
+ }
+
+ public void consumeSets(BiConsumer<T, T> consumer) {
for (T element : parent.keySet()) {
// Find root with path-compression.
T representative = findSet(element);
- unification.computeIfAbsent(representative, k -> new HashSet<>()).add(element);
+ consumer.accept(representative, element);
}
- return unification;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/DominatorChecker.java b/src/main/java/com/android/tools/r8/utils/DominatorChecker.java
index 46c6716..c2fd047 100644
--- a/src/main/java/com/android/tools/r8/utils/DominatorChecker.java
+++ b/src/main/java/com/android/tools/r8/utils/DominatorChecker.java
@@ -16,7 +16,6 @@
public interface DominatorChecker {
boolean check(BasicBlock targetBlock);
- DominatorChecker TRUE_CHECKER = targetBlock -> true;
DominatorChecker FALSE_CHECKER = targetBlock -> false;
class PrecomputedDominatorChecker implements DominatorChecker {
diff --git a/src/main/java/com/android/tools/r8/utils/IROrdering.java b/src/main/java/com/android/tools/r8/utils/IROrdering.java
index db97cfa..4d1508d 100644
--- a/src/main/java/com/android/tools/r8/utils/IROrdering.java
+++ b/src/main/java/com/android/tools/r8/utils/IROrdering.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.google.common.collect.Lists;
import java.util.Collection;
@@ -16,6 +17,8 @@
Iterable<DexEncodedMethod> order(Iterable<DexEncodedMethod> methods);
+ Iterable<DexClassAndMethod> orderClassMethods(Iterable<DexClassAndMethod> methods);
+
Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods);
Set<DexEncodedMethod> order(Set<DexEncodedMethod> methods);
@@ -36,6 +39,11 @@
}
@Override
+ public Iterable<DexClassAndMethod> orderClassMethods(Iterable<DexClassAndMethod> methods) {
+ return methods;
+ }
+
+ @Override
public Collection<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
return methods;
}
@@ -64,6 +72,13 @@
}
@Override
+ public List<DexClassAndMethod> orderClassMethods(Iterable<DexClassAndMethod> methods) {
+ List<DexClassAndMethod> toShuffle = Lists.newArrayList(methods);
+ Collections.shuffle(toShuffle);
+ return toShuffle;
+ }
+
+ @Override
public List<DexEncodedMethod> order(Collection<DexEncodedMethod> methods) {
return order((Iterable<DexEncodedMethod>) methods);
}
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 e069c30..6fe8388 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.Version;
import com.android.tools.r8.androidapi.ComputedApiLevel;
import com.android.tools.r8.cf.CfVersion;
+import com.android.tools.r8.classmerging.Policy;
import com.android.tools.r8.debuginfo.DebugRepresentation;
import com.android.tools.r8.dex.ApplicationReader.ProgramClassConflictResolver;
import com.android.tools.r8.dex.Constants;
@@ -50,6 +51,7 @@
import com.android.tools.r8.graph.AppView.WholeProgramOptimizations;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
@@ -66,7 +68,6 @@
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.horizontalclassmerging.HorizontallyMergedClasses;
-import com.android.tools.r8.horizontalclassmerging.Policy;
import com.android.tools.r8.inspector.internal.InspectorImpl;
import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -114,7 +115,6 @@
import com.android.tools.r8.verticalclassmerging.VerticalClassMergerOptions;
import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -2476,9 +2476,6 @@
// specified.
public boolean enableD8ResourcesPassThrough = false;
- // TODO(b/144781417): This is disabled by default as some test apps appear to have such classes.
- public boolean allowNonAbstractClassesWithAbstractMethods = true;
-
public boolean verifyKeptGraphInfo = false;
public boolean readInputStackMaps = true;
@@ -2502,11 +2499,10 @@
public Comparator<DexMethod> interfaceMethodOrdering = null;
- public Comparator<Wrapper<DexEncodedMethod>> getInterfaceMethodOrderingOrDefault(
- Comparator<Wrapper<DexEncodedMethod>> comparator) {
+ public Comparator<? super DexClassAndMethod> getInterfaceMethodOrderingOrDefault(
+ Comparator<DexClassAndMethod> comparator) {
if (interfaceMethodOrdering != null) {
- return (a, b) ->
- interfaceMethodOrdering.compare(a.get().getReference(), b.get().getReference());
+ return (a, b) -> interfaceMethodOrdering.compare(a.getReference(), b.getReference());
}
return comparator;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java
index 769905a..1210325 100644
--- a/src/main/java/com/android/tools/r8/utils/ListUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -297,14 +298,22 @@
void accept(T item, int index);
}
+ public static <T> List<T> sort(Iterable<T> items, Comparator<T> comparator, int numberOfItems) {
+ List<T> sorted = new ArrayList<>(numberOfItems);
+ Iterables.addAll(sorted, items);
+ sorted.sort(comparator);
+ return sorted;
+ }
+
public static <T> List<T> sort(Collection<T> items, Comparator<T> comparator) {
List<T> sorted = new ArrayList<>(items);
sorted.sort(comparator);
return sorted;
}
- public static <T> void destructiveSort(List<T> items, Comparator<T> comparator) {
+ public static <T> List<T> destructiveSort(List<T> items, Comparator<T> comparator) {
items.sort(comparator);
+ return items;
}
// Utility to add a slow verification of a comparator as part of sorting. Note that this
@@ -358,7 +367,13 @@
return true;
}
- public static <T> List<T> joinNewArrayList(List<T> one, List<T> other) {
+ public static <T> List<T> concat(List<T> one, List<T> other) {
+ if (one.isEmpty()) {
+ return other;
+ }
+ if (other.isEmpty()) {
+ return one;
+ }
ArrayList<T> ts = new ArrayList<>(one.size() + other.size());
ts.addAll(one);
ts.addAll(other);
diff --git a/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java b/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java
index b04dd82..c6056ea 100644
--- a/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapConsumerUtils.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MapConsumer;
-import com.android.tools.r8.naming.ProguardMapMarkerInfo;
import java.util.function.Function;
public class MapConsumerUtils {
@@ -19,12 +18,9 @@
}
return new MapConsumer() {
@Override
- public void accept(
- DiagnosticsHandler diagnosticsHandler,
- ProguardMapMarkerInfo makerInfo,
- ClassNameMapper classNameMapper) {
- existingMapConsumer.accept(diagnosticsHandler, makerInfo, classNameMapper);
- newConsumer.accept(diagnosticsHandler, makerInfo, classNameMapper);
+ public void accept(DiagnosticsHandler diagnosticsHandler, ClassNameMapper classNameMapper) {
+ existingMapConsumer.accept(diagnosticsHandler, classNameMapper);
+ newConsumer.accept(diagnosticsHandler, classNameMapper);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramMethodEquivalence.java b/src/main/java/com/android/tools/r8/utils/ProgramMethodEquivalence.java
index cdb98b8..91553b8 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramMethodEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramMethodEquivalence.java
@@ -18,7 +18,6 @@
}
@Override
- @SuppressWarnings("ReferenceEquality")
protected boolean doEquivalent(ProgramMethod method, ProgramMethod other) {
return method.getDefinition() == other.getDefinition();
}
diff --git a/src/main/java/com/android/tools/r8/utils/ValueUtils.java b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
index 509816c..68d3364 100644
--- a/src/main/java/com/android/tools/r8/utils/ValueUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ValueUtils.java
@@ -170,8 +170,11 @@
// Ensure that all paths from new-array-empty to |usage| contain all array-put instructions.
DominatorChecker dominatorChecker = DominatorChecker.create(definition.getBlock(), usageBlock);
- for (Instruction user : arrayValue.uniqueUsers()) {
- if (!dominatorChecker.check(user.getBlock())) {
+ // Visit in reverse order because array-puts generally appear in order, and DominatorChecker's
+ // cache is more effective when visiting in reverse order.
+ for (int i = arraySize - 1; i >= 0; --i) {
+ ArrayPut arrayPut = arrayPutsByIndex[i];
+ if (arrayPut != null && !dominatorChecker.check(arrayPut.getBlock())) {
return null;
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java
index 522ff17..ae458e4 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndFieldMapBase.java
@@ -18,7 +18,7 @@
}
@Override
- Wrapper<K> wrap(K field) {
+ protected Wrapper<K> wrap(K field) {
return DexClassAndFieldEquivalence.get().wrap(field);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
index eb747e4..6dd73cf 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMemberMap.java
@@ -5,8 +5,12 @@
package com.android.tools.r8.utils.collections;
import com.android.tools.r8.graph.DexClassAndMember;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.TriPredicate;
import com.google.common.base.Equivalence.Wrapper;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.BiConsumer;
@@ -15,6 +19,7 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.stream.Stream;
public abstract class DexClassAndMemberMap<K extends DexClassAndMember<?, ?>, V> {
@@ -37,7 +42,11 @@
}
public V computeIfAbsent(K member, Function<K, V> fn) {
- return backing.computeIfAbsent(wrap(member), key -> fn.apply(key.get()));
+ return computeIfAbsent(wrap(member), fn);
+ }
+
+ public V computeIfAbsent(Wrapper<K> wrapper, Function<K, V> fn) {
+ return backing.computeIfAbsent(wrapper, key -> fn.apply(key.get()));
}
public boolean containsKey(K member) {
@@ -48,6 +57,10 @@
backing.forEach((wrapper, value) -> consumer.accept(wrapper.get(), value));
}
+ public void forEachKey(Consumer<K> consumer) {
+ backing.keySet().forEach(wrapper -> consumer.accept(wrapper.get()));
+ }
+
public void forEachValue(Consumer<V> consumer) {
backing.values().forEach(consumer);
}
@@ -56,6 +69,16 @@
return backing.get(wrap(member));
}
+ public V get(Wrapper<K> wrapper) {
+ return backing.get(wrapper);
+ }
+
+ public List<K> getKeysSorted() {
+ List<K> keys = new ArrayList<>(size());
+ backing.keySet().forEach(key -> keys.add(key.get()));
+ return ListUtils.sort(keys, (x, y) -> x.getReference().compareTo(y.getReference()));
+ }
+
public V getOrDefault(K member, V defaultValue) {
return backing.getOrDefault(wrap(member), defaultValue);
}
@@ -94,5 +117,25 @@
return backing.size();
}
- abstract Wrapper<K> wrap(K member);
+ public Stream<K> streamKeys() {
+ return streamWrappedKeys().map(Wrapper::get);
+ }
+
+ public Stream<Wrapper<K>> streamWrappedKeys() {
+ return backing.keySet().stream();
+ }
+
+ public <TB, TC> TraversalContinuation<TB, TC> traverse(
+ BiFunction<K, V, TraversalContinuation<TB, TC>> fn) {
+ for (Entry<Wrapper<K>, V> entry : backing.entrySet()) {
+ TraversalContinuation<TB, TC> traversalContinuation =
+ fn.apply(entry.getKey().get(), entry.getValue());
+ if (traversalContinuation.shouldBreak()) {
+ return traversalContinuation;
+ }
+ }
+ return TraversalContinuation.doContinue();
+ }
+
+ protected abstract Wrapper<K> wrap(K member);
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodMap.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodMap.java
new file mode 100644
index 0000000..0728fc0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodMap.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2023, 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.collections;
+
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.utils.DexClassAndMethodEquivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+public class DexClassAndMethodMap<V> extends DexClassAndMemberMap<DexClassAndMethod, V> {
+
+ private static final DexClassAndMethodMap<?> EMPTY = new DexClassAndMethodMap<>(ImmutableMap::of);
+
+ private DexClassAndMethodMap(Supplier<Map<Wrapper<DexClassAndMethod>, V>> backingFactory) {
+ super(backingFactory);
+ }
+
+ protected DexClassAndMethodMap(Map<Wrapper<DexClassAndMethod>, V> backing) {
+ super(backing);
+ }
+
+ public static <V> DexClassAndMethodMap<V> create() {
+ return new DexClassAndMethodMap<>(HashMap::new);
+ }
+
+ public static <V> DexClassAndMethodMap<V> create(int capacity) {
+ return new DexClassAndMethodMap<>(new HashMap<>(capacity));
+ }
+
+ public static <V> DexClassAndMethodMap<V> createConcurrent() {
+ return new DexClassAndMethodMap<>(ConcurrentHashMap::new);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <V> DexClassAndMethodMap<V> empty() {
+ return (DexClassAndMethodMap<V>) EMPTY;
+ }
+
+ @Override
+ protected Wrapper<DexClassAndMethod> wrap(DexClassAndMethod method) {
+ return DexClassAndMethodEquivalence.get().wrap(method);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
index 007aa51..9b77365 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexClassAndMethodSetBase.java
@@ -42,7 +42,6 @@
@Override
public boolean add(T method) {
T existing = backing.put(method.getReference(), method);
- assert existing == null || existing.isStructurallyEqualTo(method);
return existing == null;
}
diff --git a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
index b5c3c26..5a0a814 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/ProgramMethodMap.java
@@ -43,7 +43,7 @@
}
@Override
- Wrapper<ProgramMethod> wrap(ProgramMethod method) {
+ protected Wrapper<ProgramMethod> wrap(ProgramMethod method) {
return ProgramMethodEquivalence.get().wrap(method);
}
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
index bf8a075..a198871 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ClassMerger.java
@@ -37,18 +37,14 @@
import com.android.tools.r8.graph.GenericSignatureCorrectnessHelper;
import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.ObjectUtils;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Streams;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -57,9 +53,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
-import java.util.stream.Stream;
class ClassMerger {
@@ -73,34 +67,33 @@
OptimizationFeedback.getSimpleFeedback();
private final AppView<AppInfoWithLiveness> appView;
- private final VerticalClassMergerGraphLens.Builder deferredRenamings;
private final DexItemFactory dexItemFactory;
private final VerticalClassMergerGraphLens.Builder lensBuilder;
+ private final VerticalClassMergerGraphLens.Builder outerLensBuilder;
private final VerticallyMergedClasses.Builder verticallyMergedClassesBuilder;
private final DexProgramClass source;
private final DexProgramClass target;
- private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
-
- private boolean abortMerge = false;
+ private final List<SynthesizedBridgeCode> synthesizedBridges;
ClassMerger(
AppView<AppInfoWithLiveness> appView,
- VerticalClassMergerGraphLens.Builder lensBuilder,
+ VerticalClassMergerGraphLens.Builder outerLensBuilder,
+ List<SynthesizedBridgeCode> synthesizedBridges,
VerticallyMergedClasses.Builder verticallyMergedClassesBuilder,
- DexProgramClass source,
- DexProgramClass target) {
+ VerticalMergeGroup group) {
this.appView = appView;
- this.deferredRenamings = new VerticalClassMergerGraphLens.Builder();
this.dexItemFactory = appView.dexItemFactory();
- this.lensBuilder = lensBuilder;
+ this.lensBuilder = new VerticalClassMergerGraphLens.Builder();
+ this.outerLensBuilder = outerLensBuilder;
+ this.synthesizedBridges = synthesizedBridges;
this.verticallyMergedClassesBuilder = verticallyMergedClassesBuilder;
- this.source = source;
- this.target = target;
+ this.source = group.getSource();
+ this.target = group.getTarget();
}
- public boolean merge() throws ExecutionException {
+ public void merge() {
// Merge the class [clazz] into [targetClass] by adding all methods to
// targetClass that are not currently contained.
// Step 1: Merge methods
@@ -137,7 +130,7 @@
availableMethodSignatures,
definition.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
- deferredRenamings.recordMove(directMethod.getDefinition(), resultingDirectMethod);
+ lensBuilder.recordMove(directMethod.getDefinition(), resultingDirectMethod);
blockRedirectionOfSuperCalls(resultingDirectMethod);
// Private methods in the parent class may be targeted with invoke-super if the two
@@ -146,10 +139,8 @@
&& definition.isPrivate()
&& AccessControl.isMemberAccessible(directMethod, source, target, appView)
.isTrue()) {
- // TODO(b/315283465): Add a test for correct rewriting of invoke-super to nest members
- // and determine if we need to record something here or not.
- // deferredRenamings.mapVirtualMethodToDirectInType(
- // directMethod.getReference(), target.getType());
+ lensBuilder.mapVirtualMethodToDirectInType(
+ definition.getReference(), definition, target.getType());
}
}
});
@@ -161,7 +152,7 @@
// Remove abstract/interface methods that are shadowed. The identity mapping below is
// needed to ensure we correctly fixup the mapping in case the signature refers to
// merged classes.
- deferredRenamings.recordSplit(virtualMethod, shadowedBy, null, null);
+ lensBuilder.recordSplit(virtualMethod, shadowedBy, null, null);
// The override now corresponds to the method in the parent, so unset its synthetic flag
// if the method in the parent is not synthetic.
@@ -171,28 +162,16 @@
continue;
}
} else {
- if (abortMerge) {
- // If [virtualMethod] does not resolve to a single method in [target], abort.
- assert restoreDebuggingState(
- Streams.concat(directMethods.values().stream(), virtualMethods.values().stream()));
- return false;
- }
-
// The method is not shadowed. If it is abstract, we can simply move it to the subclass.
// Non-abstract methods are handled below (they cannot simply be moved to the subclass as
// a virtual method, because they might be the target of an invoke-super instruction).
if (virtualMethod.isAbstract()) {
- // Abort if target is non-abstract and does not override the abstract method.
- if (!target.isAbstract()) {
- assert appView.options().testing.allowNonAbstractClassesWithAbstractMethods;
- abortMerge = true;
- return false;
- }
+ assert target.isAbstract();
// Update the holder of [virtualMethod] using renameMethod().
DexEncodedMethod resultingVirtualMethod =
renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
resultingVirtualMethod.setLibraryMethodOverride(virtualMethod.isLibraryMethodOverride());
- deferredRenamings.recordMove(virtualMethod, resultingVirtualMethod);
+ lensBuilder.recordMove(virtualMethod, resultingVirtualMethod);
add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
continue;
}
@@ -261,13 +240,7 @@
.getMethodInfo(virtualMethod, source)
.joiner())));
- deferredRenamings.recordSplit(virtualMethod, override, bridge, resultingMethod);
- }
-
- if (abortMerge) {
- assert restoreDebuggingState(
- Streams.concat(directMethods.values().stream(), virtualMethods.values().stream()));
- return false;
+ lensBuilder.recordSplit(virtualMethod, override, bridge, resultingMethod);
}
// Rewrite generic signatures before we merge a base with a generic signature.
@@ -349,12 +322,12 @@
target.setNestMemberAttributes(source.getNestMembersClassAttributes());
}
// Step 6: Record merging.
- assert !abortMerge;
assert GenericSignatureCorrectnessHelper.createForVerification(
appView, GenericSignatureContextBuilder.createForSingleClass(appView, target))
.evaluateSignaturesForClass(target)
.isValid();
- return true;
+ outerLensBuilder.merge(lensBuilder);
+ verticallyMergedClassesBuilder.add(source, target);
}
/**
@@ -495,31 +468,6 @@
type -> true);
}
- private boolean restoreDebuggingState(Stream<DexEncodedMethod> toBeDiscarded) {
- toBeDiscarded.forEach(
- method -> {
- assert !method.isObsolete();
- method.setObsolete();
- });
- source.forEachMethod(
- method -> {
- if (method.isObsolete()) {
- method.unsetObsolete();
- }
- });
- assert Streams.concat(Streams.stream(source.methods()), Streams.stream(target.methods()))
- .allMatch(method -> !method.isObsolete());
- return true;
- }
-
- public VerticalClassMergerGraphLens.Builder getRenamings() {
- return deferredRenamings;
- }
-
- public List<SynthesizedBridgeCode> getSynthesizedBridges() {
- return synthesizedBridges;
- }
-
private void redirectSuperCallsInTarget(DexEncodedMethod oldTarget) {
DexMethod oldTargetReference = oldTarget.getReference();
if (source.isInterface()) {
@@ -530,8 +478,7 @@
// rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()",
// if I has a supertype J. This is due to the fact that invoke-super instructions that
// resolve to a method on an interface never hit an implementation below that interface.
- deferredRenamings.mapVirtualMethodToDirectInType(
- oldTargetReference, oldTarget, target.getType());
+ lensBuilder.mapVirtualMethodToDirectInType(oldTargetReference, oldTarget, target.getType());
} else {
// If we merge class B into class C, and class C contains an invocation super.m(), then it
// is insufficient to rewrite "invoke-super B.m()" to "invoke-{direct,virtual} C.m$B()" (the
@@ -548,8 +495,7 @@
boolean resolutionSucceeds =
appView.appInfo().resolveMethodOnClass(holder, signatureInHolder).isSingleResolution();
if (resolutionSucceeds) {
- deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInHolder, oldTarget, target.type);
+ lensBuilder.mapVirtualMethodToDirectInType(signatureInHolder, oldTarget, target.type);
} else {
break;
}
@@ -567,12 +513,11 @@
// TODO(b/315283244): Should not rely on lens for this. Instead precompute this before
// merging any classes.
boolean resolutionSucceededBeforeMerge =
- lensBuilder.hasMappingForSignatureInContext(holder, signatureInType)
+ outerLensBuilder.hasMappingForSignatureInContext(holder, signatureInType)
|| appView.appInfo().lookupSuperTarget(signatureInHolder, holder, appView)
!= null;
if (resolutionSucceededBeforeMerge) {
- deferredRenamings.mapVirtualMethodToDirectInType(
- signatureInType, oldTarget, target.type);
+ lensBuilder.mapVirtualMethodToDirectInType(signatureInType, oldTarget, target.type);
}
}
holder =
@@ -600,7 +545,7 @@
// class C extends B {
// public void m() { super.m(); } <- invoke needs to be rewritten to invoke-direct
// }
- deferredRenamings.markMethodAsMerged(method);
+ lensBuilder.markMethodAsMerged(method);
}
private DexEncodedMethod buildBridgeMethod(
@@ -647,20 +592,15 @@
// Returns the method that shadows the given method, or null if method is not shadowed.
private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
- SingleResolutionResult<?> resolutionResult =
- appView.appInfo().resolveMethodOnLegacy(target, method.getReference()).asSingleResolution();
- if (resolutionResult == null) {
- // May happen in case of missing classes, or if multiple implementations were found.
- abortMerge = true;
- return null;
+ DexEncodedMethod resolvedMethod =
+ appView.appInfo().resolveMethodOnLegacy(target, method.getReference()).getResolvedMethod();
+ assert resolvedMethod != null;
+ if (resolvedMethod != method) {
+ assert resolvedMethod.isVirtualMethod() == method.isVirtualMethod();
+ return resolvedMethod;
}
- DexEncodedMethod actual = resolutionResult.getResolvedMethod();
- if (ObjectUtils.notIdentical(actual, method)) {
- assert actual.isVirtualMethod() == method.isVirtualMethod();
- return actual;
- }
- // The method is not actually overridden. This means that we will move `method` to the
- // subtype. If `method` is abstract, then so should the subtype be.
+ // The method is not actually overridden. This means that we will move `method` to the subtype.
+ // If `method` is abstract, then so should the subtype be.
return null;
}
@@ -694,7 +634,7 @@
for (DexEncodedField field : sourceFields) {
DexEncodedField resultingField = renameFieldIfNeeded(field, availableFieldSignatures);
existingFieldNames.add(resultingField.getName());
- deferredRenamings.recordMove(field, resultingField);
+ lensBuilder.recordMove(field, resultingField);
result[i] = resultingField;
i++;
}
@@ -732,7 +672,7 @@
DexEncodedMethod result =
method.toTypeSubstitutedMethodAsInlining(newSignature, dexItemFactory);
result.getMutableOptimizationInfo().markForceInline();
- deferredRenamings.recordMove(method, result);
+ lensBuilder.recordMove(method, result);
// Renamed constructors turn into ordinary private functions. They can be private, as
// they are only references from their direct subclass, which they were merged into.
result.getAccessFlags().unsetConstructor();
@@ -823,10 +763,6 @@
private void makeStatic(DexEncodedMethod method) {
method.getAccessFlags().setStatic();
- if (!method.getCode().isCfCode()) {
- // Due to member rebinding we may have inserted bridge methods with synthesized code.
- // Currently, there is no easy way to make such code static.
- abortMerge = true;
- }
+ assert method.getCode().isCfCode();
}
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
index 22316f3c..fdd9383 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/ConnectedComponentVerticalClassMerger.java
@@ -4,20 +4,17 @@
package com.android.tools.r8.verticalclassmerging;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ListUtils;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Comparator;
import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ExecutionException;
public class ConnectedComponentVerticalClassMerger {
private final AppView<AppInfoWithLiveness> appView;
- private final Set<DexProgramClass> classesToMerge;
+ private final Collection<VerticalMergeGroup> classesToMerge;
// The resulting graph lens that should be used after class merging.
private final VerticalClassMergerGraphLens.Builder lensBuilder;
@@ -29,7 +26,7 @@
VerticallyMergedClasses.builder();
ConnectedComponentVerticalClassMerger(
- AppView<AppInfoWithLiveness> appView, Set<DexProgramClass> classesToMerge) {
+ AppView<AppInfoWithLiveness> appView, Collection<VerticalMergeGroup> classesToMerge) {
this.appView = appView;
this.classesToMerge = classesToMerge;
this.lensBuilder = new VerticalClassMergerGraphLens.Builder();
@@ -39,35 +36,16 @@
return classesToMerge.isEmpty();
}
- public VerticalClassMergerResult.Builder run(ImmediateProgramSubtypingInfo immediateSubtypingInfo)
- throws ExecutionException {
- List<DexProgramClass> classesToMergeSorted =
- ListUtils.sort(classesToMerge, Comparator.comparing(DexProgramClass::getType));
- for (DexProgramClass clazz : classesToMergeSorted) {
- mergeClassIfPossible(clazz, immediateSubtypingInfo);
+ public VerticalClassMergerResult.Builder run() {
+ List<VerticalMergeGroup> classesToMergeSorted =
+ ListUtils.sort(classesToMerge, Comparator.comparing(group -> group.getSource().getType()));
+ for (VerticalMergeGroup group : classesToMergeSorted) {
+ ClassMerger classMerger =
+ new ClassMerger(
+ appView, lensBuilder, synthesizedBridges, verticallyMergedClassesBuilder, group);
+ classMerger.merge();
}
return VerticalClassMergerResult.builder(
lensBuilder, synthesizedBridges, verticallyMergedClassesBuilder);
}
-
- private void mergeClassIfPossible(
- DexProgramClass sourceClass, ImmediateProgramSubtypingInfo immediateSubtypingInfo)
- throws ExecutionException {
- List<DexProgramClass> subclasses = immediateSubtypingInfo.getSubclasses(sourceClass);
- assert subclasses.size() == 1;
- DexProgramClass targetClass = ListUtils.first(subclasses);
- if (verticallyMergedClassesBuilder.isMergeSource(targetClass)
- || verticallyMergedClassesBuilder.isMergeTarget(sourceClass)) {
- return;
- }
- ClassMerger merger =
- new ClassMerger(
- appView, lensBuilder, verticallyMergedClassesBuilder, sourceClass, targetClass);
- if (merger.merge()) {
- verticallyMergedClassesBuilder.add(sourceClass, targetClass);
- // Commit the changes to the graph lens.
- lensBuilder.merge(merger.getRenamings());
- synthesizedBridges.addAll(merger.getSynthesizedBridges());
- }
- }
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/InvokeSpecialToDefaultLibraryMethodUseRegistry.java b/src/main/java/com/android/tools/r8/verticalclassmerging/InvokeSpecialToDefaultLibraryMethodUseRegistry.java
index 506a2af..8c76978 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/InvokeSpecialToDefaultLibraryMethodUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/InvokeSpecialToDefaultLibraryMethodUseRegistry.java
@@ -10,7 +10,7 @@
public class InvokeSpecialToDefaultLibraryMethodUseRegistry
extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
- InvokeSpecialToDefaultLibraryMethodUseRegistry(
+ public InvokeSpecialToDefaultLibraryMethodUseRegistry(
AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
super(appView, context, false);
assert context.getHolder().isInterface();
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/MergeMayLeadToNoSuchMethodErrorUseRegistry.java b/src/main/java/com/android/tools/r8/verticalclassmerging/MergeMayLeadToNoSuchMethodErrorUseRegistry.java
index c69002e..3c20af4 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/MergeMayLeadToNoSuchMethodErrorUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/MergeMayLeadToNoSuchMethodErrorUseRegistry.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.graph.lens.MethodLookupResult;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-class MergeMayLeadToNoSuchMethodErrorUseRegistry
+public class MergeMayLeadToNoSuchMethodErrorUseRegistry
extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
private final AppView<AppInfoWithLiveness> appViewWithLiveness;
@@ -22,7 +22,7 @@
private final GraphLens codeLens;
private final DexProgramClass source;
- MergeMayLeadToNoSuchMethodErrorUseRegistry(
+ public MergeMayLeadToNoSuchMethodErrorUseRegistry(
AppView<AppInfoWithLiveness> appView, ProgramMethod context, DexProgramClass source) {
super(appView, context, Boolean.FALSE);
assert context.getHolder().getSuperType().isIdenticalTo(source.getType());
@@ -32,7 +32,7 @@
this.source = source;
}
- boolean mayLeadToNoSuchMethodError() {
+ public boolean mayLeadToNoSuchMethodError() {
return getResult();
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
index cb482b9..9b0626b 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -182,6 +182,7 @@
}
private void run(ExecutorService executorService, Timing timing) throws ExecutionException {
+ appView.appInfo().getMethodAccessInfoCollection().verifyNoNonResolving(appView);
timing.begin("Setup");
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
@@ -224,8 +225,7 @@
Collection<ConnectedComponentVerticalClassMerger> connectedComponentMergers =
getConnectedComponentMergers(
connectedComponents, immediateSubtypingInfo, executorService, timing);
- return applyConnectedComponentMergers(
- connectedComponentMergers, immediateSubtypingInfo, executorService, timing);
+ return applyConnectedComponentMergers(connectedComponentMergers, executorService, timing);
}
private Collection<ConnectedComponentVerticalClassMerger> getConnectedComponentMergers(
@@ -244,8 +244,9 @@
connectedComponent -> {
Timing threadTiming = Timing.create("Compute classes to merge in component", options);
ConnectedComponentVerticalClassMerger connectedComponentMerger =
- new VerticalClassMergerPolicyExecutor(appView, pinnedClasses)
- .run(connectedComponent, immediateSubtypingInfo);
+ new VerticalClassMergerPolicyExecutor(
+ appView, immediateSubtypingInfo, pinnedClasses)
+ .run(connectedComponent, executorService, threadTiming);
if (!connectedComponentMerger.isEmpty()) {
synchronized (connectedComponentMergers) {
connectedComponentMergers.add(connectedComponentMerger);
@@ -263,7 +264,6 @@
private VerticalClassMergerResult applyConnectedComponentMergers(
Collection<ConnectedComponentVerticalClassMerger> connectedComponentMergers,
- ImmediateProgramSubtypingInfo immediateSubtypingInfo,
ExecutorService executorService,
Timing timing)
throws ExecutionException {
@@ -276,7 +276,7 @@
connectedComponentMerger -> {
Timing threadTiming = Timing.create("Merge classes in component", options);
VerticalClassMergerResult.Builder verticalClassMergerComponentResult =
- connectedComponentMerger.run(immediateSubtypingInfo);
+ connectedComponentMerger.run();
verticalClassMergerResult.merge(verticalClassMergerComponentResult);
threadTiming.end();
return threadTiming;
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
index ad653bd..a3d1257 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerGraphLens.java
@@ -281,8 +281,7 @@
return InvokeType.STATIC;
}
if (type.isInterface()
- && mergedClasses.hasInterfaceBeenMergedIntoClass(
- previousMethod.getHolderType(), newMethod.getHolderType())) {
+ && mergedClasses.hasInterfaceBeenMergedIntoClass(previousMethod.getHolderType())) {
return InvokeType.VIRTUAL;
}
return type;
@@ -429,8 +428,14 @@
for (Entry<DexMethod, DexMethod> innerEntry : entry.getValue().entrySet()) {
DexMethod virtualMethod = innerEntry.getValue();
DexMethod implementationMethod = extraNewMethodSignatures.get(virtualMethod);
- assert implementationMethod != null;
- innerEntry.setValue(implementationMethod);
+ if (implementationMethod != null) {
+ // Handle invoke-super to non-private virtual method.
+ innerEntry.setValue(implementationMethod);
+ } else {
+ // Handle invoke-super to private virtual method (nest access).
+ assert newMethodSignatures.containsKey(virtualMethod);
+ innerEntry.setValue(newMethodSignatures.get(virtualMethod));
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
index e2bfb17..bab7656 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerPolicyExecutor.java
@@ -3,436 +3,135 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.verticalclassmerging;
-import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
-
-import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
-import com.android.tools.r8.androidapi.ComputedApiLevel;
-import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
+import com.android.tools.r8.classmerging.Policy;
+import com.android.tools.r8.classmerging.PolicyExecutor;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
-import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
-import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
-import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
-import com.android.tools.r8.profile.startup.optimization.StartupBoundaryOptimizationUtils;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.shaking.MainDexInfo;
-import com.android.tools.r8.utils.Box;
-import com.android.tools.r8.utils.FieldSignatureEquivalence;
-import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
-import com.android.tools.r8.utils.ObjectUtils;
-import com.android.tools.r8.utils.TraversalContinuation;
-import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.verticalclassmerging.policies.NoAbstractMethodsOnAbstractClassesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoAnnotationClassesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoClassInitializationChangesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoDirectlyInstantiatedClassesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoEnclosingMethodAttributesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoFieldResolutionChangesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoIllegalAccessesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoInnerClassAttributesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoInterfacesWithUnknownSubtypesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoInvokeSuperNoSuchMethodErrorsPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoKeptClassesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoLockMergingPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoMethodResolutionChangesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoNestedMergingPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoNonSerializableClassIntoSerializableClassPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.NoServiceInterfacesPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SafeConstructorInliningPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameApiReferenceLevelPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameFeatureSplitPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameMainDexGroupPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameNestPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SameStartupPartitionPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.SuccessfulVirtualMethodResolutionInTargetPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.VerticalClassMergerPolicy;
+import com.android.tools.r8.verticalclassmerging.policies.VerticalClassMergerPolicyWithPreprocessing;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
-// TODO(b/315252934): Parallelize policy execution over connected program components.
-public class VerticalClassMergerPolicyExecutor {
+public class VerticalClassMergerPolicyExecutor extends PolicyExecutor<VerticalMergeGroup> {
private final AppView<AppInfoWithLiveness> appView;
- private final InternalOptions options;
- private final MainDexInfo mainDexInfo;
+ private final ImmediateProgramSubtypingInfo immediateSubtypingInfo;
private final Set<DexProgramClass> pinnedClasses;
VerticalClassMergerPolicyExecutor(
- AppView<AppInfoWithLiveness> appView, Set<DexProgramClass> pinnedClasses) {
+ AppView<AppInfoWithLiveness> appView,
+ ImmediateProgramSubtypingInfo immediateSubtypingInfo,
+ Set<DexProgramClass> pinnedClasses) {
this.appView = appView;
- this.options = appView.options();
- this.mainDexInfo = appView.appInfo().getMainDexInfo();
+ this.immediateSubtypingInfo = immediateSubtypingInfo;
this.pinnedClasses = pinnedClasses;
}
ConnectedComponentVerticalClassMerger run(
- Set<DexProgramClass> connectedComponent,
- ImmediateProgramSubtypingInfo immediateSubtypingInfo) {
- Set<DexProgramClass> mergeCandidates = Sets.newIdentityHashSet();
- for (DexProgramClass sourceClass : connectedComponent) {
- List<DexProgramClass> subclasses = immediateSubtypingInfo.getSubclasses(sourceClass);
- if (subclasses.size() != 1) {
- continue;
- }
- DexProgramClass targetClass = ListUtils.first(subclasses);
- if (!isMergeCandidate(sourceClass, targetClass)) {
- continue;
- }
- if (!isStillMergeCandidate(sourceClass, targetClass)) {
- continue;
- }
- if (mergeMayLeadToIllegalAccesses(sourceClass, targetClass)
- || mergeMayLeadToNoSuchMethodError(sourceClass, targetClass)) {
- continue;
- }
- mergeCandidates.add(sourceClass);
- }
- return new ConnectedComponentVerticalClassMerger(appView, mergeCandidates);
+ Set<DexProgramClass> connectedComponent, ExecutorService executorService, Timing timing)
+ throws ExecutionException {
+ Collection<VerticalMergeGroup> groups =
+ createInitialMergeGroupsWithDeterministicOrder(connectedComponent);
+ Collection<Policy> policies =
+ List.of(
+ new NoDirectlyInstantiatedClassesPolicy(appView),
+ new NoInterfacesWithUnknownSubtypesPolicy(appView),
+ new NoKeptClassesPolicy(appView, pinnedClasses),
+ new SameFeatureSplitPolicy(appView),
+ new SameStartupPartitionPolicy(appView),
+ new NoServiceInterfacesPolicy(appView),
+ new NoAnnotationClassesPolicy(),
+ new NoNonSerializableClassIntoSerializableClassPolicy(appView),
+ new SafeConstructorInliningPolicy(appView),
+ new NoEnclosingMethodAttributesPolicy(),
+ new NoInnerClassAttributesPolicy(),
+ new SameNestPolicy(),
+ new SameMainDexGroupPolicy(appView),
+ new NoLockMergingPolicy(appView),
+ new SameApiReferenceLevelPolicy(appView),
+ new NoFieldResolutionChangesPolicy(appView),
+ new NoMethodResolutionChangesPolicy(appView),
+ new NoIllegalAccessesPolicy(appView),
+ new NoClassInitializationChangesPolicy(appView),
+ new NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy(appView),
+ new NoInvokeSuperNoSuchMethodErrorsPolicy(appView),
+ new SuccessfulVirtualMethodResolutionInTargetPolicy(appView),
+ new NoAbstractMethodsOnAbstractClassesPolicy(appView),
+ new NoNestedMergingPolicy());
+ groups = run(groups, policies, executorService, timing);
+ return new ConnectedComponentVerticalClassMerger(appView, groups);
}
- // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
- // method do not change in response to any class merges.
- private boolean isMergeCandidate(DexProgramClass sourceClass, DexProgramClass targetClass) {
- assert targetClass != null;
- ObjectAllocationInfoCollection allocationInfo =
- appView.appInfo().getObjectAllocationInfoCollection();
- if (allocationInfo.isInstantiatedDirectly(sourceClass)
- || allocationInfo.isInterfaceWithUnknownSubtypeHierarchy(sourceClass)
- || allocationInfo.isImmediateInterfaceOfInstantiatedLambda(sourceClass)
- || !appView.getKeepInfo(sourceClass).isVerticalClassMergingAllowed(options)
- || pinnedClasses.contains(sourceClass)) {
- return false;
- }
-
- assert sourceClass
- .traverseProgramMembers(
- member -> {
- assert !appView.getKeepInfo(member).isPinned(options);
- return TraversalContinuation.doContinue();
- })
- .shouldContinue();
-
- if (!FeatureSplitBoundaryOptimizationUtils.isSafeForVerticalClassMerging(
- sourceClass, targetClass, appView)) {
- return false;
- }
- if (!StartupBoundaryOptimizationUtils.isSafeForVerticalClassMerging(
- sourceClass, targetClass, appView)) {
- return false;
- }
- if (appView.appServices().allServiceTypes().contains(sourceClass.getType())
- && appView.getKeepInfo(targetClass).isPinned(options)) {
- return false;
- }
- if (sourceClass.isAnnotation()) {
- return false;
- }
- if (!sourceClass.isInterface()
- && targetClass.isSerializable(appView)
- && !appView.appInfo().isSerializable(sourceClass.getType())) {
- // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
- // 1.10 The Serializable Interface
- // ...
- // A Serializable class must do the following:
- // ...
- // * Have access to the no-arg constructor of its first non-serializable superclass
- return false;
- }
-
- // If there is a constructor in the target, make sure that all source constructors can be
- // inlined.
- if (!Iterables.isEmpty(targetClass.programInstanceInitializers())) {
- TraversalContinuation<?, ?> result =
- sourceClass.traverseProgramInstanceInitializers(
- method -> TraversalContinuation.breakIf(disallowInlining(method, targetClass)));
- if (result.shouldBreak()) {
- return false;
+ private Collection<VerticalMergeGroup> createInitialMergeGroupsWithDeterministicOrder(
+ Set<DexProgramClass> connectedComponent) {
+ List<VerticalMergeGroup> groups = new ArrayList<>();
+ for (DexProgramClass mergeCandidate : connectedComponent) {
+ List<DexProgramClass> subclasses = immediateSubtypingInfo.getSubclasses(mergeCandidate);
+ if (subclasses.size() == 1) {
+ groups.add(new VerticalMergeGroup(mergeCandidate, ListUtils.first(subclasses)));
}
}
- if (sourceClass.hasEnclosingMethodAttribute() || !sourceClass.getInnerClasses().isEmpty()) {
- return false;
- }
- // We abort class merging when merging across nests or from a nest to non-nest.
- // Without nest this checks null == null.
- if (ObjectUtils.notIdentical(targetClass.getNestHost(), sourceClass.getNestHost())) {
- return false;
- }
-
- // If there is an invoke-special to a default interface method and we are not merging into an
- // interface, then abort, since invoke-special to a virtual class method requires desugaring.
- if (sourceClass.isInterface() && !targetClass.isInterface()) {
- TraversalContinuation<?, ?> result =
- sourceClass.traverseProgramMethods(
- method -> {
- boolean foundInvokeSpecialToDefaultLibraryMethod =
- method.registerCodeReferencesWithResult(
- new InvokeSpecialToDefaultLibraryMethodUseRegistry(appView, method));
- return TraversalContinuation.breakIf(foundInvokeSpecialToDefaultLibraryMethod);
- });
- if (result.shouldBreak()) {
- return false;
- }
- }
-
- // Check with main dex classes to see if we are allowed to merge.
- if (!mainDexInfo.canMerge(sourceClass, targetClass, appView.getSyntheticItems())) {
- return false;
- }
-
- return true;
+ return ListUtils.destructiveSort(
+ groups, Comparator.comparing(group -> group.getSource().getType()));
}
- /**
- * Returns true if {@param sourceClass} is a merge candidate. Note that the result of the checks
- * in this method may change in response to class merges. Therefore, this method should always be
- * called before merging {@param sourceClass} into {@param targetClass}.
- */
- boolean isStillMergeCandidate(DexProgramClass sourceClass, DexProgramClass targetClass) {
- // For interface types, this is more complicated, see:
- // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5
- // We basically can't move the clinit, since it is not called when implementing classes have
- // their clinit called - except when the interface has a default method.
- if ((sourceClass.hasClassInitializer() && targetClass.hasClassInitializer())
- || targetClass.classInitializationMayHaveSideEffects(
- appView, type -> type.isIdenticalTo(sourceClass.getType()))
- || (sourceClass.isInterface()
- && sourceClass.classInitializationMayHaveSideEffects(appView))) {
- return false;
+ @Override
+ protected LinkedList<VerticalMergeGroup> apply(
+ Policy policy, LinkedList<VerticalMergeGroup> linkedGroups, ExecutorService executorService)
+ throws ExecutionException {
+ if (policy.isVerticalClassMergerPolicy()) {
+ return apply(policy.asVerticalClassMergerPolicy(), linkedGroups);
+ } else {
+ assert policy.isVerticalClassMergerPolicyWithPreprocessing();
+ return apply(policy.asVerticalClassMergerPolicyWithPreprocessing(), linkedGroups);
}
- boolean sourceCanBeSynchronizedOn =
- appView.appInfo().isLockCandidate(sourceClass)
- || sourceClass.hasStaticSynchronizedMethods();
- boolean targetCanBeSynchronizedOn =
- appView.appInfo().isLockCandidate(targetClass)
- || targetClass.hasStaticSynchronizedMethods();
- if (sourceCanBeSynchronizedOn && targetCanBeSynchronizedOn) {
- return false;
- }
- if (targetClass.hasEnclosingMethodAttribute() || !targetClass.getInnerClasses().isEmpty()) {
- return false;
- }
- if (methodResolutionMayChange(sourceClass, targetClass)) {
- return false;
- }
- // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
- // to the super class.
- if (fieldResolutionMayChange(sourceClass, targetClass)) {
- return false;
- }
- // Only merge if api reference level of source class is equal to target class. The check is
- // somewhat expensive.
- if (appView.options().apiModelingOptions().isApiCallerIdentificationEnabled()) {
- AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
- ComputedApiLevel sourceApiLevel =
- getApiReferenceLevelForMerging(apiLevelCompute, sourceClass);
- ComputedApiLevel targetApiLevel =
- getApiReferenceLevelForMerging(apiLevelCompute, targetClass);
- if (!sourceApiLevel.equals(targetApiLevel)) {
- return false;
- }
- }
- return true;
}
- private boolean disallowInlining(ProgramMethod method, DexProgramClass context) {
- if (!appView.options().inlinerOptions().enableInlining) {
- return true;
- }
- Code code = method.getDefinition().getCode();
- if (code.isCfCode()) {
- CfCode cfCode = code.asCfCode();
- ConstraintWithTarget constraint =
- cfCode.computeInliningConstraint(appView, appView.graphLens(), method);
- if (constraint.isNever()) {
- return true;
- }
- // Constructors can have references beyond the root main dex classes. This can increase the
- // size of the main dex dependent classes and we should bail out.
- if (mainDexInfo.disallowInliningIntoContext(appView, context, method)) {
- return true;
- }
- return false;
- }
- if (code.isDefaultInstanceInitializerCode()) {
- return false;
- }
- return true;
+ private LinkedList<VerticalMergeGroup> apply(
+ VerticalClassMergerPolicy policy, LinkedList<VerticalMergeGroup> linkedGroups) {
+ linkedGroups.removeIf(group -> !policy.canMerge(group));
+ return linkedGroups;
}
- private boolean fieldResolutionMayChange(DexClass source, DexClass target) {
- if (source.getType().isIdenticalTo(target.getSuperType())) {
- // If there is a "iget Target.f" or "iput Target.f" instruction in target, and the class
- // Target implements an interface that declares a static final field f, this should yield an
- // IncompatibleClassChangeError.
- // TODO(christofferqa): In the following we only check if a static field from an interface
- // shadows an instance field from [source]. We could actually check if there is an iget/iput
- // instruction whose resolution would be affected by the merge. The situation where a static
- // field shadows an instance field is probably not widespread in practice, though.
- FieldSignatureEquivalence equivalence = FieldSignatureEquivalence.get();
- Set<Wrapper<DexField>> staticFieldsInInterfacesOfTarget = new HashSet<>();
- for (DexType interfaceType : target.getInterfaces()) {
- DexClass clazz = appView.definitionFor(interfaceType);
- for (DexEncodedField staticField : clazz.staticFields()) {
- staticFieldsInInterfacesOfTarget.add(equivalence.wrap(staticField.getReference()));
- }
- }
- for (DexEncodedField instanceField : source.instanceFields()) {
- if (staticFieldsInInterfacesOfTarget.contains(
- equivalence.wrap(instanceField.getReference()))) {
- // An instruction "iget Target.f" or "iput Target.f" that used to hit a static field in an
- // interface would now hit an instance field from [source], so that an IncompatibleClass-
- // ChangeError would no longer be thrown. Abort merge.
- return true;
- }
- }
- }
- return false;
- }
-
- private boolean mergeMayLeadToIllegalAccesses(DexProgramClass source, DexProgramClass target) {
- if (source.isSamePackage(target)) {
- // When merging two classes from the same package, we only need to make sure that [source]
- // does not get less visible, since that could make a valid access to [source] from another
- // package illegal after [source] has been merged into [target].
- assert source.getAccessFlags().isPackagePrivateOrPublic();
- assert target.getAccessFlags().isPackagePrivateOrPublic();
- // TODO(b/287891322): Allow merging if `source` is only accessed from inside its own package.
- return source.getAccessFlags().isPublic() && target.getAccessFlags().isPackagePrivate();
- }
-
- // Check that all accesses to [source] and its members from inside the current package of
- // [source] will continue to work. This is guaranteed if [target] is public and all members of
- // [source] are either private or public.
- //
- // (Deliberately not checking all accesses to [source] since that would be expensive.)
- if (!target.isPublic()) {
- return true;
- }
- for (DexType sourceInterface : source.getInterfaces()) {
- DexClass sourceInterfaceClass = appView.definitionFor(sourceInterface);
- if (sourceInterfaceClass != null && !sourceInterfaceClass.isPublic()) {
- return true;
- }
- }
- for (DexEncodedField field : source.fields()) {
- if (!(field.isPublic() || field.isPrivate())) {
- return true;
- }
- }
- for (DexEncodedMethod method : source.methods()) {
- if (!(method.isPublic() || method.isPrivate())) {
- return true;
- }
- // Check if the target is overriding and narrowing the access.
- if (method.isPublic()) {
- DexEncodedMethod targetOverride = target.lookupVirtualMethod(method.getReference());
- if (targetOverride != null && !targetOverride.isPublic()) {
- return true;
- }
- }
- }
- // Check that all accesses from [source] to classes or members from the current package of
- // [source] will continue to work. This is guaranteed if the methods of [source] do not access
- // any private or protected classes or members from the current package of [source].
- TraversalContinuation<?, ?> result =
- source.traverseProgramMethods(
- method -> {
- boolean foundIllegalAccess =
- method.registerCodeReferencesWithResult(
- new IllegalAccessDetector(appView, method));
- if (foundIllegalAccess) {
- return TraversalContinuation.doBreak();
- }
- return TraversalContinuation.doContinue();
- },
- DexEncodedMethod::hasCode);
- return result.shouldBreak();
- }
-
- // TODO: maybe skip this check if target does not implement any interfaces (directly or
- // indirectly)?
- private boolean mergeMayLeadToNoSuchMethodError(DexProgramClass source, DexProgramClass target) {
- // This only returns true when an invoke-super instruction is found that targets a default
- // interface method.
- if (!options.canUseDefaultAndStaticInterfaceMethods()) {
- return false;
- }
- // This problem may only arise when merging (non-interface) classes into classes.
- if (source.isInterface() || target.isInterface()) {
- return false;
- }
- return target
- .traverseProgramMethods(
- method -> {
- MergeMayLeadToNoSuchMethodErrorUseRegistry registry =
- new MergeMayLeadToNoSuchMethodErrorUseRegistry(appView, method, source);
- method.registerCodeReferencesWithResult(registry);
- return TraversalContinuation.breakIf(registry.mayLeadToNoSuchMethodError());
- },
- DexEncodedMethod::hasCode)
- .shouldBreak();
- }
-
- private boolean methodResolutionMayChange(DexProgramClass source, DexProgramClass target) {
- for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) {
- DexEncodedMethod directTargetMethod =
- target.lookupDirectMethod(virtualSourceMethod.getReference());
- if (directTargetMethod != null) {
- // A private method shadows a virtual method. This situation is rare, since it is not
- // allowed by javac. Therefore, we just give up in this case. (In principle, it would be
- // possible to rename the private method in the subclass, and then move the virtual method
- // to the subclass without changing its name.)
- return true;
- }
- }
-
- // When merging an interface into a class, all instructions on the form "invoke-interface
- // [source].m" are changed into "invoke-virtual [target].m". We need to abort the merge if this
- // transformation could hide IncompatibleClassChangeErrors.
- if (source.isInterface() && !target.isInterface()) {
- List<DexEncodedMethod> defaultMethods = new ArrayList<>();
- for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
- if (!virtualMethod.accessFlags.isAbstract()) {
- defaultMethods.add(virtualMethod);
- }
- }
-
- // For each of the default methods, the subclass [target] could inherit another default method
- // with the same signature from another interface (i.e., there is a conflict). In such cases,
- // instructions on the form "invoke-interface [source].foo()" will fail with an Incompatible-
- // ClassChangeError.
- //
- // Example:
- // interface I1 { default void m() {} }
- // interface I2 { default void m() {} }
- // class C implements I1, I2 {
- // ... invoke-interface I1.m ... <- IncompatibleClassChangeError
- // }
- for (DexEncodedMethod method : defaultMethods) {
- // Conservatively find all possible targets for this method.
- LookupResultSuccess lookupResult =
- appView
- .appInfo()
- .resolveMethodOnInterfaceLegacy(method.getHolderType(), method.getReference())
- .lookupVirtualDispatchTargets(target, appView)
- .asLookupResultSuccess();
- assert lookupResult != null;
- if (lookupResult == null) {
- return true;
- }
- if (lookupResult.contains(method)) {
- Box<Boolean> found = new Box<>(false);
- lookupResult.forEach(
- interfaceTarget -> {
- if (ObjectUtils.identical(interfaceTarget.getDefinition(), method)) {
- return;
- }
- DexClass enclosingClass = interfaceTarget.getHolder();
- if (enclosingClass != null && enclosingClass.isInterface()) {
- // Found a default method that is different from the one in [source], aborting.
- found.set(true);
- }
- },
- lambdaTarget -> {
- // The merger should already have excluded lambda implemented interfaces.
- assert false;
- });
- if (found.get()) {
- return true;
- }
- }
- }
- }
- return false;
+ private <T> LinkedList<VerticalMergeGroup> apply(
+ VerticalClassMergerPolicyWithPreprocessing<T> policy,
+ LinkedList<VerticalMergeGroup> linkedGroups) {
+ T data = policy.preprocess(linkedGroups);
+ linkedGroups.removeIf(group -> !policy.canMerge(group, data));
+ return linkedGroups;
}
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
index 6b9955c..d814695 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMergerTreeFixer.java
@@ -46,6 +46,19 @@
}
@Override
+ public void preprocess() {
+ appView
+ .getKeepInfo()
+ .forEachPinnedMethod(
+ method -> {
+ if (!method.isInstanceInitializer(dexItemFactory)) {
+ keptSignatures.add(method);
+ }
+ },
+ appView.options());
+ }
+
+ @Override
public void postprocess() {
lensBuilder.fixupContextualVirtualToDirectMethodMaps();
}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalMergeGroup.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalMergeGroup.java
new file mode 100644
index 0000000..1b434ea
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalMergeGroup.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2023, 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.verticalclassmerging;
+
+import com.android.tools.r8.classmerging.MergeGroup;
+import com.android.tools.r8.graph.DexProgramClass;
+
+public class VerticalMergeGroup extends MergeGroup {
+
+ private final DexProgramClass source;
+ private final DexProgramClass target;
+
+ VerticalMergeGroup(DexProgramClass source, DexProgramClass target) {
+ this.source = source;
+ this.target = target;
+ }
+
+ public DexProgramClass getSource() {
+ return source;
+ }
+
+ public DexProgramClass getTarget() {
+ return target;
+ }
+
+ @Override
+ public int size() {
+ return 2;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java
index 0003478..9728275 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticallyMergedClasses.java
@@ -84,8 +84,8 @@
return mergedClasses.containsKey(type);
}
- public boolean hasInterfaceBeenMergedIntoClass(DexType interfaceType, DexType classType) {
- return classType.isIdenticalTo(mergedInterfacesToClasses.get(interfaceType));
+ public boolean hasInterfaceBeenMergedIntoClass(DexType interfaceType) {
+ return mergedInterfacesToClasses.containsKey(interfaceType);
}
public boolean hasInterfaceBeenMergedIntoSubtype(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoAbstractMethodsOnAbstractClassesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoAbstractMethodsOnAbstractClassesPolicy.java
new file mode 100644
index 0000000..f6d7d4b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoAbstractMethodsOnAbstractClassesPolicy.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoAbstractMethodsOnAbstractClassesPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NoAbstractMethodsOnAbstractClassesPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ if (!group.getSource().isAbstract() || group.getTarget().isAbstract()) {
+ return true;
+ }
+ for (ProgramMethod method :
+ group.getSource().virtualProgramMethods(DexEncodedMethod::isAbstract)) {
+ DexClassAndMethod resolvedMethod =
+ appView
+ .appInfo()
+ .resolveMethodOn(group.getTarget(), method.getReference())
+ .getResolutionPair();
+ // If the method and resolved method are different, then the abstract method will be removed
+ // and references will be rewritten to the resolved method.
+ if (resolvedMethod.getDefinition() == method.getDefinition()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "NoAbstractMethodsOnAbstractClassesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoAnnotationClassesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoAnnotationClassesPolicy.java
new file mode 100644
index 0000000..0c35617
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoAnnotationClassesPolicy.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoAnnotationClassesPolicy extends VerticalClassMergerPolicy {
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ return !group.getSource().isAnnotation();
+ }
+
+ @Override
+ public String getName() {
+ return "NoAnnotationClassesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoClassInitializationChangesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoClassInitializationChangesPolicy.java
new file mode 100644
index 0000000..d8b6933
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoClassInitializationChangesPolicy.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoClassInitializationChangesPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NoClassInitializationChangesPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ // For interface types, this is more complicated, see:
+ // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5
+ // We basically can't move the clinit, since it is not called when implementing classes have
+ // their clinit called - except when the interface has a default method.
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ return (!sourceClass.hasClassInitializer() || !targetClass.hasClassInitializer())
+ && !targetClass.classInitializationMayHaveSideEffects(
+ appView, type -> type.isIdenticalTo(sourceClass.getType()))
+ && (!sourceClass.isInterface()
+ || !sourceClass.classInitializationMayHaveSideEffects(appView));
+ }
+
+ @Override
+ public String getName() {
+ return "NoClassInitializationChangesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoDirectlyInstantiatedClassesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoDirectlyInstantiatedClassesPolicy.java
new file mode 100644
index 0000000..8487780
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoDirectlyInstantiatedClassesPolicy.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoDirectlyInstantiatedClassesPolicy extends VerticalClassMergerPolicy {
+
+ private final ObjectAllocationInfoCollection allocationInfo;
+
+ public NoDirectlyInstantiatedClassesPolicy(AppView<AppInfoWithLiveness> appView) {
+ allocationInfo = appView.appInfo().getObjectAllocationInfoCollection();
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ return !allocationInfo.isInstantiatedDirectly(sourceClass);
+ }
+
+ @Override
+ public String getName() {
+ return "NoDirectlyInstantiatedClassesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoEnclosingMethodAttributesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoEnclosingMethodAttributesPolicy.java
new file mode 100644
index 0000000..c0273cc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoEnclosingMethodAttributesPolicy.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoEnclosingMethodAttributesPolicy extends VerticalClassMergerPolicy {
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ return !group.getSource().hasEnclosingMethodAttribute()
+ && !group.getTarget().hasEnclosingMethodAttribute();
+ }
+
+ @Override
+ public String getName() {
+ return "NoEnclosingMethodAttributesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoFieldResolutionChangesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoFieldResolutionChangesPolicy.java
new file mode 100644
index 0000000..7d38449
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoFieldResolutionChangesPolicy.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.FieldSignatureEquivalence;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.HashSet;
+import java.util.Set;
+
+public class NoFieldResolutionChangesPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NoFieldResolutionChangesPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
+ // to the super class.
+ return !fieldResolutionMayChange(group.getSource(), group.getTarget());
+ }
+
+ private boolean fieldResolutionMayChange(DexClass source, DexClass target) {
+ if (source.getType().isIdenticalTo(target.getSuperType())) {
+ // If there is a "iget Target.f" or "iput Target.f" instruction in target, and the class
+ // Target implements an interface that declares a static final field f, this should yield an
+ // IncompatibleClassChangeError.
+ // TODO(christofferqa): In the following we only check if a static field from an interface
+ // shadows an instance field from [source]. We could actually check if there is an iget/iput
+ // instruction whose resolution would be affected by the merge. The situation where a static
+ // field shadows an instance field is probably not widespread in practice, though.
+ FieldSignatureEquivalence equivalence = FieldSignatureEquivalence.get();
+ Set<Wrapper<DexField>> staticFieldsInInterfacesOfTarget = new HashSet<>();
+ for (DexType interfaceType : target.getInterfaces()) {
+ DexClass clazz = appView.definitionFor(interfaceType);
+ for (DexEncodedField staticField : clazz.staticFields()) {
+ staticFieldsInInterfacesOfTarget.add(equivalence.wrap(staticField.getReference()));
+ }
+ }
+ for (DexEncodedField instanceField : source.instanceFields()) {
+ if (staticFieldsInInterfacesOfTarget.contains(
+ equivalence.wrap(instanceField.getReference()))) {
+ // An instruction "iget Target.f" or "iput Target.f" that used to hit a static field in an
+ // interface would now hit an instance field from [source], so that an IncompatibleClass-
+ // ChangeError would no longer be thrown. Abort merge.
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getName() {
+ return "NoFieldResolutionChangesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoIllegalAccessesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoIllegalAccessesPolicy.java
new file mode 100644
index 0000000..4322b34
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoIllegalAccessesPolicy.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.verticalclassmerging.IllegalAccessDetector;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoIllegalAccessesPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NoIllegalAccessesPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ return !mergeMayLeadToIllegalAccesses(group.getSource(), group.getTarget());
+ }
+
+ private boolean mergeMayLeadToIllegalAccesses(DexProgramClass source, DexProgramClass target) {
+ if (source.isSamePackage(target)) {
+ // When merging two classes from the same package, we only need to make sure that [source]
+ // does not get less visible, since that could make a valid access to [source] from another
+ // package illegal after [source] has been merged into [target].
+ assert source.getAccessFlags().isPackagePrivateOrPublic();
+ assert target.getAccessFlags().isPackagePrivateOrPublic();
+ // TODO(b/287891322): Allow merging if `source` is only accessed from inside its own package.
+ return source.getAccessFlags().isPublic() && target.getAccessFlags().isPackagePrivate();
+ }
+
+ // Check that all accesses to [source] and its members from inside the current package of
+ // [source] will continue to work. This is guaranteed if [target] is public and all members of
+ // [source] are either private or public.
+ //
+ // (Deliberately not checking all accesses to [source] since that would be expensive.)
+ if (!target.isPublic()) {
+ return true;
+ }
+ for (DexType sourceInterface : source.getInterfaces()) {
+ DexClass sourceInterfaceClass = appView.definitionFor(sourceInterface);
+ if (sourceInterfaceClass != null && !sourceInterfaceClass.isPublic()) {
+ return true;
+ }
+ }
+ for (DexEncodedField field : source.fields()) {
+ if (!(field.isPublic() || field.isPrivate())) {
+ return true;
+ }
+ }
+ for (DexEncodedMethod method : source.methods()) {
+ if (!(method.isPublic() || method.isPrivate())) {
+ return true;
+ }
+ // Check if the target is overriding and narrowing the access.
+ if (method.isPublic()) {
+ DexEncodedMethod targetOverride = target.lookupVirtualMethod(method.getReference());
+ if (targetOverride != null && !targetOverride.isPublic()) {
+ return true;
+ }
+ }
+ }
+ // Check that all accesses from [source] to classes or members from the current package of
+ // [source] will continue to work. This is guaranteed if the methods of [source] do not access
+ // any private or protected classes or members from the current package of [source].
+ TraversalContinuation<?, ?> result =
+ source.traverseProgramMethods(
+ method -> {
+ boolean foundIllegalAccess =
+ method.registerCodeReferencesWithResult(
+ new IllegalAccessDetector(appView, method));
+ if (foundIllegalAccess) {
+ return TraversalContinuation.doBreak();
+ }
+ return TraversalContinuation.doContinue();
+ },
+ DexEncodedMethod::hasCode);
+ return result.shouldBreak();
+ }
+
+ @Override
+ public String getName() {
+ return "NoIllegalAccessesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInnerClassAttributesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInnerClassAttributesPolicy.java
new file mode 100644
index 0000000..e6b01c0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInnerClassAttributesPolicy.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoInnerClassAttributesPolicy extends VerticalClassMergerPolicy {
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ return group.getSource().getInnerClasses().isEmpty()
+ && group.getTarget().getInnerClasses().isEmpty();
+ }
+
+ @Override
+ public String getName() {
+ return "NoInnerClassAttributesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy.java
new file mode 100644
index 0000000..13b1342
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.verticalclassmerging.InvokeSpecialToDefaultLibraryMethodUseRegistry;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy
+ extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy(
+ AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ // If there is an invoke-special to a default interface method and we are not merging into an
+ // interface, then abort, since invoke-special to a virtual class method requires desugaring.
+ if (!sourceClass.isInterface() || targetClass.isInterface()) {
+ return true;
+ }
+ TraversalContinuation<?, ?> result =
+ sourceClass.traverseProgramMethods(
+ method -> {
+ boolean foundInvokeSpecialToDefaultLibraryMethod =
+ method.registerCodeReferencesWithResult(
+ new InvokeSpecialToDefaultLibraryMethodUseRegistry(appView, method));
+ return TraversalContinuation.breakIf(foundInvokeSpecialToDefaultLibraryMethod);
+ });
+ return result.shouldContinue();
+ }
+
+ @Override
+ public String getName() {
+ return "NoInterfacesWithInvokeSpecialToDefaultMethodIntoClassPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInterfacesWithUnknownSubtypesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInterfacesWithUnknownSubtypesPolicy.java
new file mode 100644
index 0000000..67fde09
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInterfacesWithUnknownSubtypesPolicy.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoInterfacesWithUnknownSubtypesPolicy extends VerticalClassMergerPolicy {
+
+ private final ObjectAllocationInfoCollection allocationInfo;
+
+ public NoInterfacesWithUnknownSubtypesPolicy(AppView<AppInfoWithLiveness> appView) {
+ allocationInfo = appView.appInfo().getObjectAllocationInfoCollection();
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ return !allocationInfo.isInterfaceWithUnknownSubtypeHierarchy(sourceClass)
+ && !allocationInfo.isImmediateInterfaceOfInstantiatedLambda(sourceClass);
+ }
+
+ @Override
+ public String getName() {
+ return "NoInterfacesWithUnknownSubtypesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInvokeSuperNoSuchMethodErrorsPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInvokeSuperNoSuchMethodErrorsPolicy.java
new file mode 100644
index 0000000..1a7ae24
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoInvokeSuperNoSuchMethodErrorsPolicy.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.verticalclassmerging.MergeMayLeadToNoSuchMethodErrorUseRegistry;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoInvokeSuperNoSuchMethodErrorsPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final InternalOptions options;
+
+ public NoInvokeSuperNoSuchMethodErrorsPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.options = appView.options();
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ return !mergeMayLeadToNoSuchMethodError(group.getSource(), group.getTarget());
+ }
+
+ private boolean mergeMayLeadToNoSuchMethodError(DexProgramClass source, DexProgramClass target) {
+ // This only returns true when an invoke-super instruction is found that targets a default
+ // interface method.
+ if (!options.canUseDefaultAndStaticInterfaceMethods()) {
+ return false;
+ }
+ // This problem may only arise when merging (non-interface) classes into classes.
+ if (source.isInterface() || target.isInterface()) {
+ return false;
+ }
+ return target
+ .traverseProgramMethods(
+ method -> {
+ MergeMayLeadToNoSuchMethodErrorUseRegistry registry =
+ new MergeMayLeadToNoSuchMethodErrorUseRegistry(appView, method, source);
+ method.registerCodeReferencesWithResult(registry);
+ return TraversalContinuation.breakIf(registry.mayLeadToNoSuchMethodError());
+ },
+ DexEncodedMethod::hasCode)
+ .shouldBreak();
+ }
+
+ @Override
+ public String getName() {
+ return "NoInvokeSuperNoSuchMethodErrorsPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java
new file mode 100644
index 0000000..959d28e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoKeptClassesPolicy.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+import java.util.Set;
+
+public class NoKeptClassesPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final Set<DexProgramClass> keptClasses;
+ private final InternalOptions options;
+
+ public NoKeptClassesPolicy(
+ AppView<AppInfoWithLiveness> appView, Set<DexProgramClass> keptClasses) {
+ this.appView = appView;
+ this.keptClasses = keptClasses;
+ this.options = appView.options();
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ return appView.getKeepInfo(sourceClass).isVerticalClassMergingAllowed(options)
+ && !keptClasses.contains(sourceClass);
+ }
+
+ @Override
+ public String getName() {
+ return "NoKeptClassesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoLockMergingPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoLockMergingPolicy.java
new file mode 100644
index 0000000..26ee674
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoLockMergingPolicy.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoLockMergingPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NoLockMergingPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ boolean sourceCanBeSynchronizedOn =
+ appView.appInfo().isLockCandidate(sourceClass)
+ || sourceClass.hasStaticSynchronizedMethods();
+ boolean targetCanBeSynchronizedOn =
+ appView.appInfo().isLockCandidate(targetClass)
+ || targetClass.hasStaticSynchronizedMethods();
+ return !sourceCanBeSynchronizedOn || !targetCanBeSynchronizedOn;
+ }
+
+ @Override
+ public String getName() {
+ return "NoLockMergingPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoMethodResolutionChangesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoMethodResolutionChangesPolicy.java
new file mode 100644
index 0000000..c51ebce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoMethodResolutionChangesPolicy.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.ObjectUtils;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+import java.util.ArrayList;
+import java.util.List;
+
+public class NoMethodResolutionChangesPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NoMethodResolutionChangesPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ return !methodResolutionMayChange(group.getSource(), group.getTarget());
+ }
+
+ private boolean methodResolutionMayChange(DexProgramClass source, DexProgramClass target) {
+ for (DexEncodedMethod virtualSourceMethod : source.virtualMethods()) {
+ DexEncodedMethod directTargetMethod =
+ target.lookupDirectMethod(virtualSourceMethod.getReference());
+ if (directTargetMethod != null) {
+ // A private method shadows a virtual method. This situation is rare, since it is not
+ // allowed by javac. Therefore, we just give up in this case. (In principle, it would be
+ // possible to rename the private method in the subclass, and then move the virtual method
+ // to the subclass without changing its name.)
+ return true;
+ }
+ }
+
+ // When merging an interface into a class, all instructions on the form "invoke-interface
+ // [source].m" are changed into "invoke-virtual [target].m". We need to abort the merge if this
+ // transformation could hide IncompatibleClassChangeErrors.
+ if (source.isInterface() && !target.isInterface()) {
+ List<DexEncodedMethod> defaultMethods = new ArrayList<>();
+ for (DexEncodedMethod virtualMethod : source.virtualMethods()) {
+ if (!virtualMethod.accessFlags.isAbstract()) {
+ defaultMethods.add(virtualMethod);
+ }
+ }
+
+ // For each of the default methods, the subclass [target] could inherit another default method
+ // with the same signature from another interface (i.e., there is a conflict). In such cases,
+ // instructions on the form "invoke-interface [source].foo()" will fail with an Incompatible-
+ // ClassChangeError.
+ //
+ // Example:
+ // interface I1 { default void m() {} }
+ // interface I2 { default void m() {} }
+ // class C implements I1, I2 {
+ // ... invoke-interface I1.m ... <- IncompatibleClassChangeError
+ // }
+ for (DexEncodedMethod method : defaultMethods) {
+ // Conservatively find all possible targets for this method.
+ LookupResultSuccess lookupResult =
+ appView
+ .appInfo()
+ .resolveMethodOnInterfaceLegacy(method.getHolderType(), method.getReference())
+ .lookupVirtualDispatchTargets(target, appView)
+ .asLookupResultSuccess();
+ assert lookupResult != null;
+ if (lookupResult == null) {
+ return true;
+ }
+ if (lookupResult.contains(method)) {
+ Box<Boolean> found = new Box<>(false);
+ lookupResult.forEach(
+ interfaceTarget -> {
+ if (ObjectUtils.identical(interfaceTarget.getDefinition(), method)) {
+ return;
+ }
+ DexClass enclosingClass = interfaceTarget.getHolder();
+ if (enclosingClass != null && enclosingClass.isInterface()) {
+ // Found a default method that is different from the one in [source], aborting.
+ found.set(true);
+ }
+ },
+ lambdaTarget -> {
+ // The merger should already have excluded lambda implemented interfaces.
+ assert false;
+ });
+ if (found.get()) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getName() {
+ return "NoMethodResolutionChangesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoNestedMergingPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoNestedMergingPolicy.java
new file mode 100644
index 0000000..55cae02
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoNestedMergingPolicy.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.utils.MapUtils;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+import java.util.Collection;
+import java.util.Map;
+
+public class NoNestedMergingPolicy
+ extends VerticalClassMergerPolicyWithPreprocessing<Map<DexProgramClass, VerticalMergeGroup>> {
+
+ @Override
+ public boolean canMerge(
+ VerticalMergeGroup group, Map<DexProgramClass, VerticalMergeGroup> groups) {
+ if (groups.containsKey(group.getTarget())) {
+ VerticalMergeGroup removedGroup = groups.remove(group.getSource());
+ assert removedGroup == group;
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Map<DexProgramClass, VerticalMergeGroup> preprocess(
+ Collection<VerticalMergeGroup> groups) {
+ return MapUtils.newIdentityHashMap(
+ builder -> {
+ for (VerticalMergeGroup group : groups) {
+ builder.put(group.getSource(), group);
+ }
+ });
+ }
+
+ @Override
+ public String getName() {
+ return "NoNestedMergingPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoNonSerializableClassIntoSerializableClassPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoNonSerializableClassIntoSerializableClassPolicy.java
new file mode 100644
index 0000000..f9c4a47
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoNonSerializableClassIntoSerializableClassPolicy.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoNonSerializableClassIntoSerializableClassPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public NoNonSerializableClassIntoSerializableClassPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
+ // 1.10 The Serializable Interface
+ // ...
+ // A Serializable class must do the following:
+ // ...
+ // * Have access to the no-arg constructor of its first non-serializable superclass
+ return sourceClass.isInterface()
+ || !targetClass.isSerializable(appView)
+ || sourceClass.isSerializable(appView);
+ }
+
+ @Override
+ public String getName() {
+ return "NoNonSerializableClassIntoSerializableClassPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoServiceInterfacesPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoServiceInterfacesPolicy.java
new file mode 100644
index 0000000..28f915f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/NoServiceInterfacesPolicy.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class NoServiceInterfacesPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final InternalOptions options;
+
+ public NoServiceInterfacesPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.options = appView.options();
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ return !appView.appServices().allServiceTypes().contains(sourceClass.getType())
+ || !appView.getKeepInfo(targetClass).isPinned(options);
+ }
+
+ @Override
+ public String getName() {
+ return "NoServiceInterfacesPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SafeConstructorInliningPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SafeConstructorInliningPolicy.java
new file mode 100644
index 0000000..15bcf11
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SafeConstructorInliningPolicy.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+import com.google.common.collect.Iterables;
+
+public class SafeConstructorInliningPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+ private final MainDexInfo mainDexInfo;
+
+ public SafeConstructorInliningPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ this.mainDexInfo = appView.appInfo().getMainDexInfo();
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ // If there is a constructor in the target, make sure that all source constructors can be
+ // inlined.
+ if (Iterables.isEmpty(targetClass.programInstanceInitializers())) {
+ return true;
+ }
+ TraversalContinuation<?, ?> result =
+ sourceClass.traverseProgramInstanceInitializers(
+ method -> TraversalContinuation.breakIf(disallowInlining(method, targetClass)));
+ return result.shouldContinue();
+ }
+
+ private boolean disallowInlining(ProgramMethod method, DexProgramClass context) {
+ if (!appView.options().inlinerOptions().enableInlining) {
+ return true;
+ }
+ Code code = method.getDefinition().getCode();
+ if (code.isCfCode()) {
+ CfCode cfCode = code.asCfCode();
+ ConstraintWithTarget constraint =
+ cfCode.computeInliningConstraint(appView, appView.graphLens(), method);
+ if (constraint.isNever()) {
+ return true;
+ }
+ // Constructors can have references beyond the root main dex classes. This can increase the
+ // size of the main dex dependent classes and we should bail out.
+ if (mainDexInfo.disallowInliningIntoContext(appView, context, method)) {
+ return true;
+ }
+ return false;
+ }
+ if (code.isDefaultInstanceInitializerCode()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "SafeConstructorInliningPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameApiReferenceLevelPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameApiReferenceLevelPolicy.java
new file mode 100644
index 0000000..736ca06
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameApiReferenceLevelPolicy.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import static com.android.tools.r8.utils.AndroidApiLevelUtils.getApiReferenceLevelForMerging;
+
+import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
+import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class SameApiReferenceLevelPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public SameApiReferenceLevelPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ assert appView.options().apiModelingOptions().isApiCallerIdentificationEnabled();
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ // Only merge if api reference level of source class is equal to target class. The check is
+ // somewhat expensive.
+ AndroidApiLevelCompute apiLevelCompute = appView.apiLevelCompute();
+ ComputedApiLevel sourceApiLevel = getApiReferenceLevelForMerging(apiLevelCompute, sourceClass);
+ ComputedApiLevel targetApiLevel = getApiReferenceLevelForMerging(apiLevelCompute, targetClass);
+ return sourceApiLevel.equals(targetApiLevel);
+ }
+
+ @Override
+ public String getName() {
+ return "SameApiReferenceLevelPolicy";
+ }
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return !appView.options().apiModelingOptions().isApiCallerIdentificationEnabled();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameFeatureSplitPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameFeatureSplitPolicy.java
new file mode 100644
index 0000000..851c5ca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameFeatureSplitPolicy.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.features.FeatureSplitBoundaryOptimizationUtils;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class SameFeatureSplitPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public SameFeatureSplitPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ return FeatureSplitBoundaryOptimizationUtils.isSafeForVerticalClassMerging(
+ sourceClass, targetClass, appView);
+ }
+
+ @Override
+ public String getName() {
+ return "SameFeatureSplitPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameMainDexGroupPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameMainDexGroupPolicy.java
new file mode 100644
index 0000000..fc8edeb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameMainDexGroupPolicy.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.synthesis.SyntheticItems;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class SameMainDexGroupPolicy extends VerticalClassMergerPolicy {
+
+ private final MainDexInfo mainDexInfo;
+ private final SyntheticItems syntheticItems;
+
+ public SameMainDexGroupPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.mainDexInfo = appView.appInfo().getMainDexInfo();
+ this.syntheticItems = appView.getSyntheticItems();
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ assert !mainDexInfo.isNone();
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ return mainDexInfo.canMerge(sourceClass, targetClass, syntheticItems);
+ }
+
+ @Override
+ public String getName() {
+ return "SameMainDexGroupPolicy";
+ }
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return mainDexInfo.isNone();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameNestPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameNestPolicy.java
new file mode 100644
index 0000000..a0cda38
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameNestPolicy.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.utils.ObjectUtils;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class SameNestPolicy extends VerticalClassMergerPolicy {
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ // We abort class merging when merging across nests or from a nest to non-nest.
+ // Without nest this checks null == null.
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ return ObjectUtils.identical(targetClass.getNestHost(), sourceClass.getNestHost());
+ }
+
+ @Override
+ public String getName() {
+ return "SameNestPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameStartupPartitionPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameStartupPartitionPolicy.java
new file mode 100644
index 0000000..6651ade
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SameStartupPartitionPolicy.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.profile.startup.optimization.StartupBoundaryOptimizationUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class SameStartupPartitionPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public SameStartupPartitionPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ DexProgramClass sourceClass = group.getSource();
+ DexProgramClass targetClass = group.getTarget();
+ return StartupBoundaryOptimizationUtils.isSafeForVerticalClassMerging(
+ sourceClass, targetClass, appView);
+ }
+
+ @Override
+ public String getName() {
+ return "SameStartupPartitionPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SuccessfulVirtualMethodResolutionInTargetPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SuccessfulVirtualMethodResolutionInTargetPolicy.java
new file mode 100644
index 0000000..a3cee74
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/SuccessfulVirtualMethodResolutionInTargetPolicy.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.MethodResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public class SuccessfulVirtualMethodResolutionInTargetPolicy extends VerticalClassMergerPolicy {
+
+ private final AppView<AppInfoWithLiveness> appView;
+
+ public SuccessfulVirtualMethodResolutionInTargetPolicy(AppView<AppInfoWithLiveness> appView) {
+ this.appView = appView;
+ }
+
+ @Override
+ public boolean canMerge(VerticalMergeGroup group) {
+ for (ProgramMethod method : group.getSource().virtualProgramMethods()) {
+ MethodResolutionResult resolutionResult =
+ appView.appInfo().resolveMethodOn(group.getTarget(), method.getReference());
+ if (!resolutionResult.isSingleResolution()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return "SuccessfulVirtualMethodResolutionInTargetPolicy";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicy.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicy.java
new file mode 100644
index 0000000..49f179f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicy.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.classmerging.Policy;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+
+public abstract class VerticalClassMergerPolicy extends Policy {
+
+ public abstract boolean canMerge(VerticalMergeGroup group);
+
+ @Override
+ public boolean isVerticalClassMergerPolicy() {
+ return true;
+ }
+
+ @Override
+ public VerticalClassMergerPolicy asVerticalClassMergerPolicy() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicyWithPreprocessing.java b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicyWithPreprocessing.java
new file mode 100644
index 0000000..7737ebc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/policies/VerticalClassMergerPolicyWithPreprocessing.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2023, 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.verticalclassmerging.policies;
+
+import com.android.tools.r8.classmerging.Policy;
+import com.android.tools.r8.verticalclassmerging.VerticalMergeGroup;
+import java.util.Collection;
+
+public abstract class VerticalClassMergerPolicyWithPreprocessing<T> extends Policy {
+
+ public abstract boolean canMerge(VerticalMergeGroup group, T data);
+
+ public abstract T preprocess(Collection<VerticalMergeGroup> groups);
+
+ @Override
+ public boolean isVerticalClassMergerPolicyWithPreprocessing() {
+ return true;
+ }
+
+ @Override
+ public VerticalClassMergerPolicyWithPreprocessing<T>
+ asVerticalClassMergerPolicyWithPreprocessing() {
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
index a0821d2..1075fd9 100644
--- a/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTool.java
@@ -74,10 +74,11 @@
KOTLINC_1_6_0("kotlin-compiler-1.6.0"),
KOTLINC_1_7_0("kotlin-compiler-1.7.0"),
KOTLINC_1_8_0("kotlin-compiler-1.8.0"),
+ KOTLINC_1_9_21("kotlin-compiler-1.9.21"),
KOTLIN_DEV("kotlin-compiler-dev");
- public static final KotlinCompilerVersion MIN_SUPPORTED_VERSION = KOTLINC_1_6_0;
- public static final KotlinCompilerVersion MAX_SUPPORTED_VERSION = KOTLINC_1_8_0;
+ public static final KotlinCompilerVersion MIN_SUPPORTED_VERSION = KOTLINC_1_7_0;
+ public static final KotlinCompilerVersion MAX_SUPPORTED_VERSION = KOTLINC_1_9_21;
private final String folder;
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 0e08d8f..b64e03a 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -86,7 +86,6 @@
private final List<Path> features = new ArrayList<>();
private Path resourceShrinkerOutput = null;
private HashMap<String, Path> resourceShrinkerOutputForFeatures = new HashMap<>();
- private PartitionMapConsumer partitionMapConsumer = null;
@Override
public boolean isR8TestBuilder() {
@@ -115,21 +114,7 @@
if (enableMinification.isFalse()) {
builder.setDisableMinification(true);
}
- StringBuilder proguardMapBuilder = new StringBuilder();
- builder.setProguardMapConsumer(
- new StringConsumer() {
- @Override
- public void accept(String string, DiagnosticsHandler handler) {
- proguardMapBuilder.append(string);
- }
-
- @Override
- public void finished(DiagnosticsHandler handler) {
- // Nothing to do.
- }
- });
- builder.setPartitionMapConsumer(partitionMapConsumer);
-
+ StringBuilder proguardMapBuilder = wrapProguardMapConsumer(builder);
if (!applyMappingMaps.isEmpty()) {
try {
Path mappingsDir = getState().getNewTempFolder();
@@ -207,6 +192,25 @@
return compileResult;
}
+ private static StringBuilder wrapProguardMapConsumer(Builder builder) {
+ StringBuilder pgMapOutput = new StringBuilder();
+ StringConsumer pgMapConsumer = builder.getProguardMapConsumer();
+ builder.setProguardMapConsumer(
+ new StringConsumer.ForwardingConsumer(pgMapConsumer) {
+ @Override
+ public void accept(String string, DiagnosticsHandler handler) {
+ super.accept(string, handler);
+ pgMapOutput.append(string);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ }
+ });
+ return pgMapOutput;
+ }
+
private static StringBuilder wrapProguardConfigConsumer(Builder builder) {
StringBuilder pgConfOutput = new StringBuilder();
StringConsumer pgConfConsumer = builder.getProguardConfigurationConsumer();
@@ -900,7 +904,7 @@
}
public T setPartitionMapConsumer(PartitionMapConsumer partitionMapConsumer) {
- this.partitionMapConsumer = partitionMapConsumer;
+ getBuilder().setPartitionMapConsumer(partitionMapConsumer);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 0515b8d..1792e97 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -196,6 +196,11 @@
return isCfRuntime() ? ToolHelper.getJava8RuntimeJar() : getDefaultAndroidJar();
}
+ public CfRuntime getCfRuntime() {
+ assert isCfRuntime();
+ return runtime.asCf();
+ }
+
// Access to underlying runtime/wrapper.
public TestRuntime getRuntime() {
return runtime;
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverrideWithInterfacePublicizerTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverrideWithInterfacePublicizerTest.java
new file mode 100644
index 0000000..8e3c527
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/PackagePrivateOverrideWithInterfacePublicizerTest.java
@@ -0,0 +1,96 @@
+// Copyright (c) 2023, 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.accessrelaxation;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.resolution.virtualtargets.package_a.ViewModel;
+import com.google.common.collect.ImmutableList;
+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;
+
+/** Regression test for b/314984596. */
+@RunWith(Parameterized.class)
+public class PackagePrivateOverrideWithInterfacePublicizerTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Main.class, ViewModel.class)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertSuccessOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, ViewModel.class)
+ .addProgramClassFileData(getTransformedClasses())
+ .addKeepMainRule(Main.class)
+ .addKeepClassRules("wtf.I")
+ .allowAccessModification()
+ .allowStdoutMessages()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertSuccessOutput);
+ }
+
+ public List<byte[]> getTransformedClasses() throws Exception {
+ return ImmutableList.of(
+ transformer(I.class).setClassDescriptor("Lwtf/I;").transform(),
+ transformer(SubViewModel.class).setImplementsClassDescriptors("Lwtf/I;").transform());
+ }
+
+ private void assertSuccessOutput(TestRunResult<?> result) {
+ if (parameters.isDexRuntime() && parameters.getDexRuntimeVersion().isDalvik()) {
+ result.assertFailureWithErrorThatMatches(containsString("overrides final"));
+ } else {
+ result.assertSuccessWithOutputLines("SubViewModel.clear()", "ViewModel.clear()");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ SubViewModel subViewModel = new SubViewModel();
+ subViewModel.clear();
+ subViewModel.clearBridge();
+ }
+ }
+
+ // Repackaged to wtf.I using transformer so that it sorts higher than the ViewModel class.
+ // This ensures that I is processed before ViewModel in the access modifier, which reproduces
+ // the bug in b/314984596.
+ public interface /*wtf.*/ I {}
+
+ @NeverClassInline
+ public static class SubViewModel extends ViewModel implements I {
+
+ @NeverInline
+ public void clear() {
+ System.out.println("SubViewModel.clear()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingMultipleGroupsTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingMultipleGroupsTest.java
index 635ff0e..b2dc326 100644
--- a/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingMultipleGroupsTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/ClinitDeadlockAfterMergingMultipleGroupsTest.java
@@ -7,9 +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.classmerging.Policy;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.horizontalclassmerging.MultiClassSameReferencePolicy;
-import com.android.tools.r8.horizontalclassmerging.Policy;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
index 513561a..662a717 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/IncorrectRewritingOfInvokeSuperTest.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.utils.BooleanUtils;
@@ -43,7 +44,10 @@
options.testing.enableVerticalClassMergerLensAssertion = verifyLensLookup;
})
.enableInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
.addDontObfuscate()
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(A.class, ArgType.class))
.setMinApi(parameters)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputLines("Caught NPE");
@@ -78,6 +82,7 @@
}
}
+ @NoVerticalClassMerging
static class B extends A {
@NeverInline
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerCollisionWithOverridesTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerCollisionWithOverridesTest.java
new file mode 100644
index 0000000..d620d5c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerCollisionWithOverridesTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.vertical;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import 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 VerticalClassMergerCollisionWithOverridesTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addProgramClassFileData(getTransformedClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("C C.m()", "B B.m()");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addProgramClassFileData(getTransformedClass())
+ .addKeepMainRule(Main.class)
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(A.class))
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("C C.m()", "B B.m()");
+ }
+
+ private static byte[] getTransformedClass() throws Exception {
+ return transformer(C.class)
+ .removeMethods(
+ (access, name, descriptor, signature, exceptions) ->
+ name.equals("m") && descriptor.equals("()" + descriptor(B.class)))
+ .transform();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ C c = new C();
+ c.callOnA();
+ c.callOnB();
+ }
+ }
+
+ abstract static class A {
+
+ abstract A m();
+
+ A callOnA() {
+ return m();
+ }
+ }
+
+ @NoVerticalClassMerging
+ abstract static class B extends A {
+
+ @NeverInline
+ B m() {
+ System.out.println("B B.m()");
+ return this;
+ }
+
+ B callOnB() {
+ return m();
+ }
+ }
+
+ @NeverClassInline
+ static class C extends B {
+
+ @NeverInline
+ @Override
+ C m() {
+ System.out.println("C C.m()");
+ return this;
+ }
+
+ // DELETED BY TRANSFORMER: synthetic bridge B m() { ...}
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInvokeInterfaceToVirtualInSuperClassTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInvokeInterfaceToVirtualInSuperClassTest.java
new file mode 100644
index 0000000..ef58833
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerInvokeInterfaceToVirtualInSuperClassTest.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2023git add, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.vertical;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import 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 VerticalClassMergerInvokeInterfaceToVirtualInSuperClassTest 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)
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(I.class))
+ .enableInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ I i = new B();
+ i.m();
+ }
+ }
+
+ interface I {
+
+ void m();
+ }
+
+ @NoVerticalClassMerging
+ static class A {
+
+ @NeverInline
+ public void m() {
+ System.out.println("Hello, world!");
+ }
+ }
+
+ static class B extends A implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerPinnedMethodCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerPinnedMethodCollisionTest.java
new file mode 100644
index 0000000..1458079
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerPinnedMethodCollisionTest.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.vertical;
+
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergerPinnedMethodCollisionTest 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(
+ "-keep class " + UserSub.class.getTypeName() + " {",
+ " void f(" + B.class.getTypeName() + ");",
+ "}")
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(A.class))
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("B", "B");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new User().f(new B());
+ new UserSub().f(new B());
+ }
+ }
+
+ static class A {}
+
+ static class B extends A {
+
+ @Override
+ public String toString() {
+ return "B";
+ }
+ }
+
+ static class User {
+
+ @NeverInline
+ void f(A a) {
+ System.out.println(a);
+ }
+ }
+
+ static class UserSub extends User {
+
+ void f(B b) {
+ System.out.println(b);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerPinnedMethodInterfaceCollisionTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerPinnedMethodInterfaceCollisionTest.java
new file mode 100644
index 0000000..84fa122
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerPinnedMethodInterfaceCollisionTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.vertical;
+
+
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import 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 VerticalClassMergerPinnedMethodInterfaceCollisionTest 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())
+ .addKeepClassAndMembersRules(Main.class)
+ .addKeepRules(
+ "-keep class " + OtherUser.class.getTypeName() + " {",
+ " void f(" + B.class.getTypeName() + ");",
+ "}")
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype(A.class))
+ .enableNoUnusedInterfaceRemovalAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("B", "B");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ keep(new UserImpl());
+ new OtherUser().f(new B());
+ }
+
+ static void keep(User user) {
+ user.f(new B());
+ }
+ }
+
+ static class A {}
+
+ static class B extends A {
+
+ @Override
+ public String toString() {
+ return "B";
+ }
+ }
+
+ @NoUnusedInterfaceRemoval
+ @NoVerticalClassMerging
+ interface User {
+
+ void f(A a);
+ }
+
+ static class UserImpl implements User {
+
+ @Override
+ public void f(A a) {
+ System.out.println(a);
+ }
+ }
+
+ static class OtherUser {
+
+ void f(B b) { // f(B)
+ System.out.println(b);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
index 7254cd5..0e87692 100644
--- a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergerTest.java
@@ -388,7 +388,7 @@
Set<String> preservedClassNames =
ImmutableSet.of(
"classmerging.NestedDefaultInterfaceMethodsTest",
- "classmerging.NestedDefaultInterfaceMethodsTest$B",
+ "classmerging.NestedDefaultInterfaceMethodsTest$A",
"classmerging.NestedDefaultInterfaceMethodsTest$C");
runTest(
testForR8(parameters.getBackend())
@@ -413,7 +413,7 @@
Set<String> preservedClassNames =
ImmutableSet.of(
"classmerging.NestedDefaultInterfaceMethodsTest",
- "classmerging.NestedDefaultInterfaceMethodsTest$B",
+ "classmerging.NestedDefaultInterfaceMethodsTest$A",
"classmerging.NestedDefaultInterfaceMethodsTest$C");
runTestOnInput(
testForR8(parameters.getBackend())
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingInvokeSuperToNestMemberTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingInvokeSuperToNestMemberTest.java
new file mode 100644
index 0000000..b8147e0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingInvokeSuperToNestMemberTest.java
@@ -0,0 +1,162 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.vertical;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoAccessModification;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.classmerging.vertical.VerticalClassMergingInvokeSuperToNestMemberTest.Foo.Bar;
+import com.android.tools.r8.classmerging.vertical.VerticalClassMergingInvokeSuperToNestMemberTest.Foo.Baz;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+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.Opcodes;
+
+@RunWith(Parameterized.class)
+public class VerticalClassMergingInvokeSuperToNestMemberTest extends TestBase {
+
+ @Parameter(0)
+ public boolean emitNestAnnotationsInDex;
+
+ @Parameter(1)
+ public boolean enableVerticalClassMerging;
+
+ @Parameter(2)
+ public TestParameters parameters;
+
+ @Parameters(name = "{2}, nest in dex: {0}, vertical: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(),
+ BooleanUtils.values(),
+ getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ assumeFalse(emitNestAnnotationsInDex);
+ assumeFalse(enableVerticalClassMerging);
+ testForJvm(parameters)
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getTransformedClasses())
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getCfRuntime().isOlderThan(CfVm.JDK11),
+ runResult ->
+ runResult.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class),
+ runResult -> runResult.assertSuccessWithOutputLines("Bar.bar()"));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || !emitNestAnnotationsInDex);
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getTransformedClasses())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.emitNestAnnotationsInDex = emitNestAnnotationsInDex)
+ .addVerticallyMergedClassesInspector(
+ inspector -> {
+ if (enableVerticalClassMerging) {
+ inspector.assertMergedIntoSubtype(Bar.class);
+ } else {
+ inspector.assertNoClassesMerged();
+ }
+ })
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .enableNoAccessModificationAnnotationsForMembers()
+ .enableNoHorizontalClassMergingAnnotations()
+ .applyIf(
+ enableVerticalClassMerging,
+ R8TestBuilder::addNoVerticalClassMergingAnnotations,
+ R8TestBuilder::enableNoVerticalClassMergingAnnotations)
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.canUseNestBasedAccesses()
+ || (parameters.isDexRuntime() && !emitNestAnnotationsInDex),
+ runResult -> runResult.assertSuccessWithOutputLines("Bar.bar()"),
+ parameters.isCfRuntime(),
+ runResult ->
+ runResult.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class),
+ runResult -> runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class));
+ }
+
+ private Collection<byte[]> getTransformedClasses() throws Exception {
+ return ImmutableList.of(
+ transformer(Foo.class).setNest(Foo.class, Bar.class, Baz.class).transform(),
+ transformer(Bar.class)
+ .setNest(Foo.class, Bar.class, Baz.class)
+ .setPrivate(Bar.class.getDeclaredMethod("bar"))
+ .transform(),
+ transformer(Baz.class)
+ .setNest(Foo.class, Bar.class, Baz.class)
+ .transformMethodInsnInMethod(
+ "test",
+ (opcode, owner, name, descriptor, isInterface, continuation) -> {
+ assertEquals(Opcodes.INVOKEVIRTUAL, opcode);
+ assertEquals(binaryName(Baz.class), owner);
+ assertEquals("bar", name);
+ continuation.visitMethodInsn(
+ Opcodes.INVOKESPECIAL, binaryName(Bar.class), name, descriptor, isInterface);
+ })
+ .transform());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Foo.test();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class Foo {
+
+ @NeverInline
+ static void test() {
+ new Baz().test();
+ }
+
+ @NoHorizontalClassMerging
+ @NoVerticalClassMerging
+ static class Bar {
+
+ @NeverInline
+ @NoAccessModification
+ /*private*/ void bar() {
+ System.out.println("Bar.bar()");
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class Baz extends Bar {
+
+ void test() {
+ bar();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingNestHostIntoNestHostTest.java b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingNestHostIntoNestHostTest.java
new file mode 100644
index 0000000..717a561
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/vertical/VerticalClassMergingNestHostIntoNestHostTest.java
@@ -0,0 +1,175 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.classmerging.vertical;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoAccessModification;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.classmerging.vertical.VerticalClassMergingNestHostIntoNestHostTest.Bar.InnerBar;
+import com.android.tools.r8.classmerging.vertical.VerticalClassMergingNestHostIntoNestHostTest.Foo.InnerFoo;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+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 VerticalClassMergingNestHostIntoNestHostTest extends TestBase {
+
+ @Parameter(0)
+ public boolean emitNestAnnotationsInDex;
+
+ @Parameter(1)
+ public TestParameters parameters;
+
+ @Parameters(name = "{1}, nest in dex: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || !emitNestAnnotationsInDex);
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getTransformedClasses())
+ .addKeepMainRule(Main.class)
+ .addOptionsModification(
+ options -> options.emitNestAnnotationsInDex = emitNestAnnotationsInDex)
+ // TODO(b/315283663): Could allow merging two classes that do not have the same nest.
+ .addVerticallyMergedClassesInspector(
+ inspector -> {
+ if (parameters.isCfRuntime() || emitNestAnnotationsInDex) {
+ inspector.assertNoClassesMerged();
+ } else {
+ inspector.assertMergedIntoSubtype(Foo.class);
+ }
+ })
+ .enableInliningAnnotations()
+ .enableNoAccessModificationAnnotationsForMembers()
+ .enableNoHorizontalClassMergingAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.isDexRuntime() && emitNestAnnotationsInDex,
+ runResult -> runResult.assertFailureWithErrorThatThrows(IllegalAccessError.class),
+ parameters.isCfRuntime() && parameters.getRuntime().asCf().isOlderThan(CfVm.JDK11),
+ runResult ->
+ runResult.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class),
+ runResult ->
+ runResult.assertSuccessWithOutputLines(
+ "InnerFoo.innerFoo()", "Foo.foo()", "InnerBar.innerBar()", "Bar.bar()"));
+ }
+
+ private Collection<byte[]> getTransformedClasses() throws Exception {
+ return ImmutableList.of(
+ transformer(Foo.class)
+ .setNest(Foo.class, InnerFoo.class)
+ .transformMethodInsnInMethod(
+ "test",
+ (opcode, owner, name, descriptor, isInterface, continuation) ->
+ continuation.visitMethodInsn(
+ opcode, owner, "innerFoo", descriptor, isInterface))
+ .transform(),
+ transformer(InnerFoo.class)
+ .setNest(Foo.class, InnerFoo.class)
+ .transformMethodInsnInMethod(
+ "test",
+ (opcode, owner, name, descriptor, isInterface, continuation) ->
+ continuation.visitMethodInsn(opcode, owner, "foo", descriptor, isInterface))
+ .transform(),
+ transformer(Bar.class)
+ .setNest(Bar.class, InnerBar.class)
+ .transformMethodInsnInMethod(
+ "test",
+ (opcode, owner, name, descriptor, isInterface, continuation) ->
+ continuation.visitMethodInsn(
+ opcode, owner, "innerBar", descriptor, isInterface))
+ .transform(),
+ transformer(InnerBar.class)
+ .setNest(Bar.class, InnerBar.class)
+ .transformMethodInsnInMethod(
+ "test",
+ (opcode, owner, name, descriptor, isInterface, continuation) ->
+ continuation.visitMethodInsn(opcode, owner, "bar", descriptor, isInterface))
+ .transform());
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Foo.test();
+ InnerFoo.test();
+ Bar.test();
+ InnerBar.test();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class Foo {
+
+ @NeverInline
+ @NoAccessModification
+ private static void foo() {
+ System.out.println("Foo.foo()");
+ }
+
+ static void test() {
+ InnerFoo.innerFoo();
+ }
+
+ @NoHorizontalClassMerging
+ static class InnerFoo {
+
+ @NeverInline
+ @NoAccessModification
+ private static void innerFoo() {
+ System.out.println("InnerFoo.innerFoo()");
+ }
+
+ static void test() {
+ Foo.foo();
+ }
+ }
+ }
+
+ @NoHorizontalClassMerging
+ static class Bar extends Foo {
+
+ @NeverInline
+ @NoAccessModification
+ private static void bar() {
+ System.out.println("Bar.bar()");
+ }
+
+ static void test() {
+ InnerBar.innerBar();
+ }
+
+ @NoHorizontalClassMerging
+ static class InnerBar {
+
+ @NeverInline
+ @NoAccessModification
+ private static void innerBar() {
+ System.out.println("InnerBar.innerBar()");
+ }
+
+ static void test() {
+ Bar.bar();
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index e043042..867d2a7 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -1572,6 +1572,14 @@
return internalInstanceField(getMirror(), thisObjectId, fieldId);
}
+ public void setFieldOnThis(String fieldName, String fieldSignature, Value value) {
+ long thisObjectId = getMirror().getThisObject(getThreadId(), getFrameId());
+ long classId = getMirror().getReferenceType(thisObjectId);
+ // TODO(zerny): Search supers too. This will only get the field if directly on the class.
+ long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+ internalSetInstanceField(getMirror(), thisObjectId, fieldId, value);
+ }
+
private long findField(VmMirror mirror, long classId, String fieldName,
String fieldSignature) {
@@ -1633,6 +1641,19 @@
}
}
+ private static void internalSetInstanceField(
+ VmMirror mirror, long objectId, long fieldId, Value value) {
+ CommandPacket commandPacket =
+ new CommandPacket(
+ ObjectReferenceCommandSet.CommandSetID, ObjectReferenceCommandSet.SetValuesCommand);
+ commandPacket.setNextValueAsObjectID(objectId);
+ commandPacket.setNextValueAsInt(1); // field count.
+ commandPacket.setNextValueAsFieldID(fieldId);
+ commandPacket.setNextValueAsUntaggedValue(value);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE : "Error code: " + replyPacket.getErrorCode();
+ }
+
private static Value internalInstanceField(VmMirror mirror, long objectId, long fieldId) {
CommandPacket commandPacket =
new CommandPacket(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalInstanceFieldCanonicalizationAfterConstructorInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalInstanceFieldCanonicalizationAfterConstructorInliningTest.java
new file mode 100644
index 0000000..d5865d7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/canonicalization/EffectivelyFinalInstanceFieldCanonicalizationAfterConstructorInliningTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2023, 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.canonicalization;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoRedundantFieldLoadElimination;
+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;
+
+/** Regression test for b/315877832. */
+@RunWith(Parameterized.class)
+public class EffectivelyFinalInstanceFieldCanonicalizationAfterConstructorInliningTest
+ 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)
+ .enableInliningAnnotations()
+ .enableNoRedundantFieldLoadEliminationAnnotations()
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Object alwaysNull = System.currentTimeMillis() > 0 ? null : new Object();
+ A a = new A(alwaysNull);
+ System.out.println(a);
+ }
+ }
+
+ static class A {
+
+ @NoRedundantFieldLoadElimination Object f;
+
+ A(Object o) {
+ f = o;
+ if (f == null) {
+ f = "Hello";
+ }
+ print(f);
+ }
+
+ @NeverInline
+ static void print(Object o) {
+ System.out.print(o);
+ }
+
+ @Override
+ public String toString() {
+ return ", world!";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsTest.java
new file mode 100644
index 0000000..2384387
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/EmptyVarargsTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2023, 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.library;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EmptyVarargsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public EmptyVarargsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepAnnotation()
+ .addKeepRules("-keepclassmembers class * { @com.android.tools.r8.Keep *; }")
+ .enableInliningAnnotations()
+ .setMinApi(parameters)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClassSubject = inspector.clazz(Main.class);
+ assertThat(mainClassSubject, isPresent());
+
+ MethodSubject testMethod =
+ mainClassSubject.uniqueMethodWithOriginalName("testDeclared");
+ assertTrue(testMethod.streamInstructions().noneMatch(InstructionSubject::isNewArray));
+ testMethod = mainClassSubject.uniqueMethodWithOriginalName("testNonDeclared");
+ assertTrue(testMethod.streamInstructions().noneMatch(InstructionSubject::isNewArray));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("hi", "hi");
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ try {
+ testDeclared();
+ testNonDeclared();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Keep
+ public Main() {}
+
+ @Keep
+ public void doPrint() {
+ System.out.println("hi");
+ }
+
+ @NeverInline
+ static void testNonDeclared() throws Exception {
+ Constructor<Main> ctor = Main.class.getConstructor();
+ Main instance = ctor.newInstance();
+ Method method = Main.class.getMethod("doPrint");
+ method.invoke(instance);
+ }
+
+ @NeverInline
+ static void testDeclared() throws Exception {
+ Constructor<Main> ctor = Main.class.getDeclaredConstructor();
+ Main instance = ctor.newInstance();
+ Method method = Main.class.getDeclaredMethod("doPrint");
+ method.invoke(instance);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
new file mode 100644
index 0000000..3468b4c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
@@ -0,0 +1,192 @@
+// Copyright (c) 2023, 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.keepanno.classpatterns;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.TypePattern;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassNamePatternsTest extends TestBase {
+
+ static final Class<?> A1 = com.android.tools.r8.keepanno.classpatterns.pkg1.A.class;
+ static final Class<?> B1 = com.android.tools.r8.keepanno.classpatterns.pkg1.B.class;
+ static final Class<?> A2 = com.android.tools.r8.keepanno.classpatterns.pkg2.A.class;
+ static final Class<?> B2 = com.android.tools.r8.keepanno.classpatterns.pkg2.B.class;
+
+ static final String EXPECTED_ALL = StringUtils.lines("pkg1.A", "pkg1.B", "pkg2.A", "pkg2.B");
+ static final String EXPECTED_PKG = StringUtils.lines("pkg1.A", "pkg1.B");
+ static final String EXPECTED_NAME = StringUtils.lines("pkg1.B", "pkg2.B");
+ static final String EXPECTED_SINGLE = StringUtils.lines("pkg2.A");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public ClassNamePatternsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getBaseInputClasses())
+ .addProgramClasses(TestAll.class)
+ .run(parameters.getRuntime(), TestAll.class)
+ .assertSuccessWithOutput(EXPECTED_ALL);
+ }
+
+ private void runTestR8(Class<?> mainClass, String expected) throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getBaseInputClasses())
+ .addProgramClasses(mainClass)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), mainClass)
+ .assertSuccessWithOutput(expected);
+ }
+
+ @Test
+ public void testAllR8() throws Exception {
+ runTestR8(TestAll.class, EXPECTED_ALL);
+ }
+
+ @Test
+ public void testPkgR8() throws Exception {
+ runTestR8(TestPkg.class, EXPECTED_PKG);
+ }
+
+ @Test
+ public void testNameR8() throws Exception {
+ runTestR8(TestName.class, EXPECTED_NAME);
+ }
+
+ @Test
+ public void testSingleR8() throws Exception {
+ runTestR8(TestSingle.class, EXPECTED_SINGLE);
+ }
+
+ public List<Class<?>> getBaseInputClasses() {
+ return ImmutableList.of(Util.class, A1, B1, A2, B2);
+ }
+
+ static class Util {
+ private static void lookupClassesAndInvokeMethods() {
+ for (String pkg : Arrays.asList("pkg1", "pkg2")) {
+ for (String name : Arrays.asList("A", "B")) {
+ String type = "com.android.tools.r8.keepanno.classpatterns." + pkg + "." + name;
+ try {
+ Class<?> clazz = Class.forName(type);
+ System.out.println(clazz.getDeclaredMethod("foo").invoke(null));
+ } catch (ClassNotFoundException ignored) {
+ } catch (IllegalAccessException ignored) {
+ } catch (InvocationTargetException ignored) {
+ } catch (NoSuchMethodException ignored) {
+ }
+ }
+ }
+ }
+ }
+
+ static class TestAll {
+
+ @UsesReflection({
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ // The empty class pattern is equivalent to "any class".
+ classNamePattern = @ClassNamePattern(),
+ methodName = "foo",
+ methodReturnTypeConstant = String.class)
+ })
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestAll().foo();
+ }
+ }
+
+ static class TestPkg {
+
+ @UsesReflection({
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ classNamePattern =
+ @ClassNamePattern(packageName = "com.android.tools.r8.keepanno.classpatterns.pkg1"),
+ methodName = "foo",
+ methodReturnTypeConstant = String.class)
+ })
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestPkg().foo();
+ }
+ }
+
+ static class TestName {
+
+ @UsesReflection({
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ classNamePattern = @ClassNamePattern(simpleName = "B"),
+ methodName = "foo",
+ methodReturnTypeConstant = String.class)
+ })
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestName().foo();
+ }
+ }
+
+ static class TestSingle {
+
+ @UsesReflection(
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ classNamePattern =
+ @ClassNamePattern(
+ simpleName = "A",
+ packageName = "com.android.tools.r8.keepanno.classpatterns.pkg2"),
+ methodName = "foo",
+ methodReturnTypePattern =
+ @TypePattern(
+ classNamePattern =
+ @ClassNamePattern(packageName = "java.lang", simpleName = "String"))))
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestSingle().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java
new file mode 100644
index 0000000..eab0e5f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.keepanno.classpatterns.pkg1;
+
+public class A {
+
+ public static String foo() {
+ return "pkg1.A";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java
new file mode 100644
index 0000000..edeb6b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.keepanno.classpatterns.pkg1;
+
+public class B {
+
+ public static String foo() {
+ return "pkg1.B";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java
new file mode 100644
index 0000000..ca0667e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.keepanno.classpatterns.pkg2;
+
+public class A {
+
+ public static String foo() {
+ return "pkg2.A";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java
new file mode 100644
index 0000000..c98e746
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2023, 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.keepanno.classpatterns.pkg2;
+
+public class B {
+
+ public static String foo() {
+ return "pkg2.B";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
index bd3d82f..312e8bb 100644
--- a/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
+++ b/src/test/java/com/android/tools/r8/keepanno/utils/KeepItemAnnotationGenerator.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.cfmethodgeneration.CodeGenerationBase;
import com.android.tools.r8.keepanno.annotations.CheckOptimizedOut;
import com.android.tools.r8.keepanno.annotations.CheckRemoved;
+import com.android.tools.r8.keepanno.annotations.ClassNamePattern;
import com.android.tools.r8.keepanno.annotations.FieldAccessFlags;
import com.android.tools.r8.keepanno.annotations.KeepBinding;
import com.android.tools.r8.keepanno.annotations.KeepCondition;
@@ -24,6 +25,7 @@
import com.android.tools.r8.keepanno.annotations.UsedByNative;
import com.android.tools.r8.keepanno.annotations.UsedByReflection;
import com.android.tools.r8.keepanno.annotations.UsesReflection;
+import com.android.tools.r8.keepanno.ast.AnnotationConstants;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.base.Strings;
@@ -51,6 +53,11 @@
Generator.run();
}
+ private static final String DEFAULT_INVALID_TYPE_PATTERN =
+ "@" + simpleName(TypePattern.class) + "(name = \"\")";
+ private static final String DEFAULT_INVALID_CLASS_NAME_PATTERN =
+ "@" + simpleName(ClassNamePattern.class) + "(simpleName = \"\")";
+
public static String quote(String str) {
return "\"" + str + "\"";
}
@@ -69,6 +76,16 @@
this.name = name;
}
+ public GroupMember setType(String type) {
+ valueType = type;
+ return this;
+ }
+
+ public GroupMember setValue(String value) {
+ valueDefault = value;
+ return this;
+ }
+
@Override
public GroupMember self() {
return this;
@@ -90,50 +107,40 @@
generator.println("public static final String " + name + " = " + quote(name) + ";");
}
+ public GroupMember requiredValue(Class<?> type) {
+ assert valueDefault == null;
+ return setType(simpleName(type));
+ }
+
+ public GroupMember requiredArrayValue(Class<?> type) {
+ assert valueDefault == null;
+ return setType(simpleName(type) + "[]");
+ }
+
public GroupMember requiredStringValue() {
- assert valueDefault == null;
- return defaultType("String");
+ return requiredValue(String.class);
}
- public GroupMember requiredValueOfType(String type) {
- assert valueDefault == null;
- return defaultType(type);
+ public GroupMember defaultValue(Class<?> type, String value) {
+ setType(simpleName(type));
+ return setValue(value);
}
- public GroupMember requiredValueOfType(Class<?> type) {
- assert valueDefault == null;
- return defaultType(simpleName(type));
- }
-
- public GroupMember requiredValueOfArrayType(Class<?> type) {
- assert valueDefault == null;
- return defaultType(simpleName(type) + "[]");
- }
-
- public GroupMember defaultType(String type) {
- valueType = type;
- return this;
- }
-
- public GroupMember defaultValue(String value) {
- valueDefault = value;
- return this;
+ public GroupMember defaultArrayValue(Class<?> type, String value) {
+ setType(simpleName(type) + "[]");
+ return setValue("{" + value + "}");
}
public GroupMember defaultEmptyString() {
- return defaultType("String").defaultValue(quote(""));
+ return defaultValue(String.class, quote(""));
}
public GroupMember defaultObjectClass() {
- return defaultType("Class<?>").defaultValue("Object.class");
+ return setType("Class<?>").setValue("Object.class");
}
- public GroupMember defaultEmptyArray(String valueType) {
- return defaultType(valueType + "[]").defaultValue("{}");
- }
-
- public GroupMember defaultEmptyArray(Class<?> type) {
- return defaultEmptyArray(simpleName(type));
+ public GroupMember defaultArrayEmpty(Class<?> type) {
+ return defaultArrayValue(type, "");
}
}
@@ -279,7 +286,7 @@
private Group createBindingsGroup() {
return new Group("bindings")
- .addMember(new GroupMember("bindings").defaultEmptyArray(KeepBinding.class));
+ .addMember(new GroupMember("bindings").defaultArrayEmpty(KeepBinding.class));
}
private Group createPreconditionsGroup() {
@@ -291,7 +298,7 @@
.setDocReturn(
"The list of preconditions. "
+ "Defaults to no conditions, thus trivially/unconditionally satisfied.")
- .defaultEmptyArray(KeepCondition.class));
+ .defaultArrayEmpty(KeepCondition.class));
}
private Group createConsequencesGroup() {
@@ -300,7 +307,7 @@
new GroupMember("consequences")
.setDocTitle("Consequences that must be kept if the annotation is in effect.")
.setDocReturn("The list of target consequences.")
- .requiredValueOfArrayType(KeepTarget.class));
+ .requiredArrayValue(KeepTarget.class));
}
private Group createConsequencesAsValueGroup() {
@@ -309,7 +316,7 @@
new GroupMember("value")
.setDocTitle("Consequences that must be kept if the annotation is in effect.")
.setDocReturn("The list of target consequences.")
- .requiredValueOfArrayType(KeepTarget.class));
+ .requiredArrayValue(KeepTarget.class));
}
private Group createAdditionalPreconditionsGroup() {
@@ -320,7 +327,7 @@
.setDocReturn(
"The list of additional preconditions. "
+ "Defaults to no additional preconditions.")
- .defaultEmptyArray("KeepCondition"));
+ .defaultArrayEmpty(KeepCondition.class));
}
private Group createAdditionalTargetsGroup(String docTitle) {
@@ -331,7 +338,7 @@
.setDocReturn(
"List of additional target consequences. "
+ "Defaults to no additional target consequences.")
- .defaultEmptyArray("KeepTarget"));
+ .defaultArrayEmpty(KeepTarget.class));
}
private Group typePatternGroup() {
@@ -345,7 +352,11 @@
new GroupMember("constant")
.setDocTitle("Exact type from a class constant.")
.addParagraph("For example, {@code String.class}.")
- .defaultObjectClass());
+ .defaultObjectClass())
+ .addMember(
+ new GroupMember("classNamePattern")
+ .setDocTitle("Classes matching the class-name pattern.")
+ .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN));
// TODO(b/248408342): Add more injections on type pattern variants.
// /** Exact type name as a string to match any array with that type as member. */
// String arrayOf() default "";
@@ -366,14 +377,37 @@
// boolean anyReference() default false;
}
+ private Group classNamePatternSimpleNameGroup() {
+ return new Group("class-simple-name")
+ .addMember(
+ new GroupMember("simpleName")
+ .setDocTitle("Exact simple name of the class or interface.")
+ .addParagraph(
+ "For example, the simple name of {@code com.example.MyClass} is {@code"
+ + " MyClass}.")
+ .addParagraph("The default matches any simple name.")
+ .defaultEmptyString());
+ }
+
+ private Group classNamePatternPackageGroup() {
+ return new Group("class-package-name")
+ .addMember(
+ new GroupMember("packageName")
+ .setDocTitle("Exact package name of the class or interface.")
+ .addParagraph(
+ "For example, the package of {@code com.example.MyClass} is {@code"
+ + " com.example}.")
+ .addParagraph("The default matches any package.")
+ .defaultEmptyString());
+ }
+
private Group getKindGroup() {
return new Group(KIND_GROUP).addMember(getKindMember());
}
private static GroupMember getKindMember() {
return new GroupMember("kind")
- .defaultType("KeepItemKind")
- .defaultValue("KeepItemKind.DEFAULT")
+ .defaultValue(KeepItemKind.class, "KeepItemKind.DEFAULT")
.setDocTitle("Specify the kind of this item pattern.")
.setDocReturn("The kind for this pattern.")
.addParagraph("Possible values are:")
@@ -406,7 +440,7 @@
"The specified option constraints do not need to be preserved for the"
+ " target.")
.setDocReturn("Option constraints allowed to be modified for the target.")
- .defaultEmptyArray("KeepOption"))
+ .defaultArrayEmpty(KeepOption.class))
.addMember(
new GroupMember("disallow")
.setDeprecated("Use " + docLink(constraints()) + " instead.")
@@ -415,7 +449,7 @@
.addParagraph(
"The specified option constraints *must* be preserved for the target.")
.setDocReturn("Option constraints not allowed to be modified for the target.")
- .defaultEmptyArray("KeepOption"))
+ .defaultArrayEmpty(KeepOption.class))
.addDocFooterParagraph(
"If nothing is specified for "
+ CONSTRAINTS_GROUP
@@ -447,7 +481,7 @@
KeepConstraint.FIELD_GET,
KeepConstraint.FIELD_SET))
.setDocReturn("Usage constraints for the target.")
- .defaultEmptyArray(KeepConstraint.class);
+ .defaultArrayEmpty(KeepConstraint.class);
}
private GroupMember bindingName() {
@@ -487,10 +521,19 @@
.defaultObjectClass();
}
+ private GroupMember classNamePattern() {
+ return new GroupMember("classNamePattern")
+ .setDocTitle(
+ "Define the " + CLASS_NAME_GROUP + " pattern by reference to a class-name pattern.")
+ .setDocReturn("The class-name pattern that defines the class.")
+ .defaultValue(ClassNamePattern.class, DEFAULT_INVALID_CLASS_NAME_PATTERN);
+ }
+
private Group createClassNamePatternGroup() {
return new Group(CLASS_NAME_GROUP)
.addMember(className())
.addMember(classConstant())
+ .addMember(classNamePattern())
.addDocFooterParagraph("If none are specified the default is to match any class name.");
}
@@ -604,7 +647,7 @@
"Mutually exclusive with all field and method properties",
"as use restricts the match to both types of members.")
.setDocReturn("The member access-flag constraints that must be met.")
- .defaultEmptyArray("MemberAccessFlags"));
+ .defaultArrayEmpty(MemberAccessFlags.class));
}
private String getMutuallyExclusiveForMethodProperties() {
@@ -635,7 +678,7 @@
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any method-access flags"))
.setDocReturn("The method access-flag constraints that must be met.")
- .defaultEmptyArray("MethodAccessFlags"));
+ .defaultArrayEmpty(MethodAccessFlags.class));
}
private Group createMethodNameGroup() {
@@ -672,8 +715,7 @@
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any return type"))
.setDocReturn("The pattern of the method return type.")
- .defaultType("TypePattern")
- .defaultValue("@TypePattern(name = \"\")"));
+ .defaultValue(TypePattern.class, DEFAULT_INVALID_TYPE_PATTERN));
}
private Group createMethodParametersGroup() {
@@ -685,8 +727,7 @@
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any parameters"))
.setDocReturn("The list of qualified type names of the method parameters.")
- .defaultType("String[]")
- .defaultValue("{\"\"}"))
+ .defaultArrayValue(String.class, quote("")))
.addMember(
new GroupMember("methodParameterTypePatterns")
.setDocTitle(
@@ -694,8 +735,7 @@
.addParagraph(getMutuallyExclusiveForMethodProperties())
.addParagraph(getMethodDefaultDoc("any parameters"))
.setDocReturn("The list of type patterns for the method parameters.")
- .defaultType("TypePattern[]")
- .defaultValue("{@TypePattern(name = \"\")}"));
+ .defaultArrayValue(TypePattern.class, DEFAULT_INVALID_TYPE_PATTERN));
}
private Group createFieldAccessGroup() {
@@ -706,7 +746,7 @@
.addParagraph(getMutuallyExclusiveForFieldProperties())
.addParagraph(getFieldDefaultDoc("any field-access flags"))
.setDocReturn("The field access-flag constraints that must be met.")
- .defaultEmptyArray("FieldAccessFlags"));
+ .defaultArrayEmpty(FieldAccessFlags.class));
}
private Group createFieldNameGroup() {
@@ -742,8 +782,7 @@
.addParagraph(getMutuallyExclusiveForFieldProperties())
.addParagraph(getFieldDefaultDoc("any type"))
.setDocReturn("The type pattern for the field type.")
- .defaultType("TypePattern")
- .defaultValue("@TypePattern(name = \"\")"));
+ .defaultValue(TypePattern.class, DEFAULT_INVALID_TYPE_PATTERN));
}
private void generateClassAndMemberPropertiesWithClassAndMemberBinding() {
@@ -814,13 +853,37 @@
.printDoc(this::println);
println("@Target(ElementType.ANNOTATION_TYPE)");
println("@Retention(RetentionPolicy.CLASS)");
- println("public @interface TypePattern {");
+ println("public @interface " + simpleName(TypePattern.class) + " {");
println();
withIndent(() -> typePatternGroup().generate(this));
println();
println("}");
}
+ private void generateClassNamePattern() {
+ printCopyRight(2023);
+ printPackage("annotations");
+ printImports(ANNOTATION_IMPORTS);
+ DocPrinter.printer()
+ .setDocTitle("A pattern structure for matching names of classes and interfaces.")
+ .addParagraph(
+ "If no properties are set, the default pattern matches any name of a class or"
+ + " interface.")
+ .printDoc(this::println);
+ println("@Target(ElementType.ANNOTATION_TYPE)");
+ println("@Retention(RetentionPolicy.CLASS)");
+ println("public @interface " + simpleName(ClassNamePattern.class) + " {");
+ println();
+ withIndent(
+ () -> {
+ classNamePatternSimpleNameGroup().generate(this);
+ println();
+ classNamePatternPackageGroup().generate(this);
+ });
+ println();
+ println("}");
+ }
+
private void generateKeepBinding() {
printCopyRight(2022);
printPackage("annotations");
@@ -1151,14 +1214,13 @@
generateFieldAccessConstants();
generateTypePatternConstants();
+ generateClassNamePatternConstants();
});
println("}");
}
private void generateAnnotationConstants(Class<?> clazz) {
- String name = simpleName(clazz);
String desc = TestBase.descriptor(clazz);
- println("public static final String SIMPLE_NAME = " + quote(name) + ";");
println("public static final String DESCRIPTOR = " + quote(desc) + ";");
}
@@ -1431,6 +1493,18 @@
println();
}
+ private void generateClassNamePatternConstants() {
+ println("public static final class ClassNamePattern {");
+ withIndent(
+ () -> {
+ generateAnnotationConstants(ClassNamePattern.class);
+ classNamePatternSimpleNameGroup().generateConstants(this);
+ classNamePatternPackageGroup().generateConstants(this);
+ });
+ println("}");
+ println();
+ }
+
private static void writeFile(Path file, Consumer<Generator> fn) throws IOException {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(byteStream);
@@ -1443,26 +1517,31 @@
Files.write(Paths.get(ToolHelper.getProjectRoot()).resolve(file), formatted.getBytes());
}
+ public static Path source(Path pkg, Class<?> clazz) {
+ return pkg.resolve(simpleName(clazz) + ".java");
+ }
+
public static void run() throws IOException {
writeFile(Paths.get("doc/keepanno-guide.md"), KeepAnnoMarkdownGenerator::generateMarkdownDoc);
Path keepAnnoRoot = Paths.get("src/keepanno/java/com/android/tools/r8/keepanno");
Path astPkg = keepAnnoRoot.resolve("ast");
- writeFile(astPkg.resolve("AnnotationConstants.java"), Generator::generateConstants);
+ writeFile(source(astPkg, AnnotationConstants.class), Generator::generateConstants);
Path annoPkg = Paths.get("src/keepanno/java/com/android/tools/r8/keepanno/annotations");
- writeFile(annoPkg.resolve("TypePattern.java"), Generator::generateTypePattern);
- writeFile(annoPkg.resolve("KeepBinding.java"), Generator::generateKeepBinding);
- writeFile(annoPkg.resolve("KeepTarget.java"), Generator::generateKeepTarget);
- writeFile(annoPkg.resolve("KeepCondition.java"), Generator::generateKeepCondition);
- writeFile(annoPkg.resolve("KeepForApi.java"), Generator::generateKeepForApi);
- writeFile(annoPkg.resolve("UsesReflection.java"), Generator::generateUsesReflection);
+ writeFile(source(annoPkg, TypePattern.class), Generator::generateTypePattern);
+ writeFile(source(annoPkg, ClassNamePattern.class), Generator::generateClassNamePattern);
+ writeFile(source(annoPkg, KeepBinding.class), Generator::generateKeepBinding);
+ writeFile(source(annoPkg, KeepTarget.class), Generator::generateKeepTarget);
+ writeFile(source(annoPkg, KeepCondition.class), Generator::generateKeepCondition);
+ writeFile(source(annoPkg, KeepForApi.class), Generator::generateKeepForApi);
+ writeFile(source(annoPkg, UsesReflection.class), Generator::generateUsesReflection);
writeFile(
- annoPkg.resolve("UsedByReflection.java"),
+ source(annoPkg, UsedByReflection.class),
g -> g.generateUsedByX("UsedByReflection", "accessed reflectively"));
writeFile(
- annoPkg.resolve("UsedByNative.java"),
+ source(annoPkg, UsedByNative.class),
g -> g.generateUsedByX("UsedByNative", "accessed from native code via JNI"));
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataFirstToLatestTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataFirstToLatestTest.java
index 0248459..cc58dbe 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataFirstToLatestTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataFirstToLatestTest.java
@@ -100,6 +100,19 @@
assertThat(
assertionError.getMessage(),
containsString("compiled with an incompatible version of Kotlin"));
+ } else if (kotlinParameters.is(KotlinCompilerVersion.KOTLINC_1_6_0)) {
+ AssertionError assertionError =
+ assertThrows(
+ AssertionError.class,
+ () -> {
+ runTest(kotlinParameters.getCompiler().getCompilerVersion(), libJar, stdLibJar);
+ });
+ // TODO(b/317019265): Triage this.
+ assertThat(
+ assertionError.getMessage(),
+ containsString(
+ "Trying to inline an anonymous object which is not part of the public ABI"));
+
} else {
runTest(kotlinParameters.getCompiler().getCompilerVersion(), libJar, stdLibJar);
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
index 6954d57..d732c25 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataVersionNumberBumpTest.java
@@ -8,14 +8,17 @@
import static org.junit.Assert.fail;
import static org.objectweb.asm.Opcodes.ASM7;
+import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.KotlinCompilerTool.KotlinTargetVersion;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.R8FullTestBuilder;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StreamUtils;
import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
@@ -58,6 +61,7 @@
final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 1, 16});
testBuilder
+ .apply(this::addLibrary)
.addProgramFiles(kotlinc.getKotlinAnnotationJar())
.setMinApi(parameters)
.addOptionsModification(options -> options.testing.keepMetadataInR8IfNotRewritten = false)
@@ -72,6 +76,7 @@
final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 4, 0});
testBuilder
+ .apply(this::addLibrary)
.addProgramFiles(kotlinc.getKotlinAnnotationJar())
.setMinApi(parameters)
.addKeepAllClassesRuleWithAllowObfuscation()
@@ -85,6 +90,7 @@
final R8FullTestBuilder testBuilder = testForR8(parameters.getBackend());
rewriteMetadataVersion(testBuilder::addProgramClassFileData, new int[] {1, 4, 2});
testBuilder
+ .apply(this::addLibrary)
.addProgramFiles(kotlinc.getKotlinAnnotationJar())
.setMinApi(parameters)
.addKeepAllClassesRuleWithAllowObfuscation()
@@ -93,6 +99,16 @@
.inspect(inspector -> inspectMetadataVersion(inspector, "1.4.2"));
}
+ private void addLibrary(R8FullTestBuilder testBuilder) {
+ // Starting with version 1.6 kotlin-stdlib references java.lang.annotation.Repeatable. This
+ // annotation was added in Android N.
+ testBuilder.applyIf(
+ kotlinParameters.isNewerThanOrEqualTo(KotlinCompilerVersion.KOTLINC_1_6_0)
+ && parameters.isDexRuntime()
+ && parameters.getApiLevel().isLessThan(AndroidApiLevel.N),
+ b -> b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.N)));
+ }
+
private void rewriteMetadataVersion(Consumer<byte[]> rewrittenBytesConsumer, int[] newVersion)
throws IOException {
ZipUtils.iter(
diff --git a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
index 4292c7b..55dffb8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/optimize/switches/KotlinEnumSwitchTest.java
@@ -10,10 +10,11 @@
import static org.junit.Assert.assertNotEquals;
import com.android.tools.r8.KotlinCompilerTool;
-import com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion;
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import java.util.List;
@@ -52,6 +53,8 @@
@Test
public void test() throws Exception {
testForR8(parameters.getBackend())
+ // Use android.jar with java.lang.ClassValue.
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.U))
.addProgramFiles(
kotlinJars.getForConfiguration(kotlinParameters), kotlinc.getKotlinAnnotationJar())
.addKeepMainRule("enumswitch.EnumSwitchKt")
@@ -62,13 +65,6 @@
})
.setMinApi(parameters)
.addDontObfuscate()
- // This will probably start failing when the CL
- // https://github.com/JetBrains/kotlin/commit/79f6d4b590573e6adccd7e8899d3b15ddb42d185
- // is propagated to the build for kotlin-reflect.
- .applyIf(
- parameters.isDexRuntime()
- && kotlinParameters.isNewerThan(KotlinCompilerVersion.KOTLINC_1_8_0),
- b -> b.addDontWarn("java.lang.ClassValue"))
.allowDiagnosticWarningMessages()
.compile()
.assertAllWarningMessagesMatch(equalTo("Resource 'META-INF/MANIFEST.MF' already exists."))
diff --git a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
index 9b536f6..955048d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/reflection/KotlinReflectTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_3_72;
import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_8_0;
+import static com.android.tools.r8.KotlinCompilerTool.KotlinCompilerVersion.KOTLINC_1_9_21;
import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
import com.android.tools.r8.KotlinTestBase;
@@ -108,9 +109,11 @@
.assertNoErrorMessages()
// -keepattributes Signature is added in kotlin-reflect from version 1.4.20.
.applyIf(kotlinParameters.is(KOTLINC_1_3_72), TestCompileResult::assertNoInfoMessages)
- // TODO(b/269794485): Figure out why generic signatures fail using kotlin-dev.
+ // TODO(b/269794485): Figure out why generic signatures fail using 1.9.
.applyIf(
- kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72) && !kotlinParameters.isKotlinDev(),
+ kotlinParameters.getCompiler().isNot(KOTLINC_1_3_72)
+ && kotlinParameters.getCompiler().isNot(KOTLINC_1_9_21)
+ && !kotlinParameters.isKotlinDev(),
TestBase::verifyAllInfoFromGenericSignatureTypeParameterValidation)
.apply(KotlinMetadataTestBase::verifyExpectedWarningsFromKotlinReflectAndStdLib)
.writeToZip(foo.toPath())
diff --git a/src/test/java/com/android/tools/r8/naming/MappingHeaderContentTest.java b/src/test/java/com/android/tools/r8/naming/MappingHeaderContentTest.java
new file mode 100644
index 0000000..ae080ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/MappingHeaderContentTest.java
@@ -0,0 +1,91 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.PartitionMapConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.retrace.MappingPartition;
+import com.android.tools.r8.retrace.MappingPartitionMetadata;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MappingHeaderContentTest extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public MappingHeaderContentTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ @Test
+ public void test() throws Exception {
+ Box<MappingPartitionMetadata> metadataBox = new Box<>();
+ List<MappingPartition> partitions = new ArrayList<>();
+ StringBuilder builder = new StringBuilder();
+ testForR8(Backend.DEX)
+ .addInnerClasses(MappingHeaderContentTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(AndroidApiLevel.B)
+ .apply(b -> b.getBuilder().setProguardMapConsumer((s, unused) -> builder.append(s)))
+ .apply(
+ b ->
+ b.getBuilder()
+ .setPartitionMapConsumer(
+ new PartitionMapConsumer() {
+ @Override
+ public void acceptMappingPartition(MappingPartition mappingPartition) {
+ partitions.add(mappingPartition);
+ }
+
+ @Override
+ public void acceptMappingPartitionMetadata(
+ MappingPartitionMetadata mappingPartitionMetadata) {
+ assertNull(metadataBox.get());
+ metadataBox.set(mappingPartitionMetadata);
+ }
+ }))
+ .compile();
+ assertNotNull(metadataBox.get());
+ assertFalse(partitions.isEmpty());
+ String mapping = builder.toString();
+ List<String> mapIdLines =
+ StringUtils.splitLines(mapping).stream()
+ .filter(s -> s.startsWith("# pg_map_id:"))
+ .collect(Collectors.toList());
+ assertEquals(
+ "Expected single pg_map_id line, found multiple:\n" + String.join("\n", mapIdLines) + "\n",
+ 1,
+ mapIdLines.size());
+ }
+
+ static class A {}
+
+ static class B {}
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(args.length == 0 ? A.class : B.class);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/InitializerNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/InitializerNumberUnboxingTest.java
new file mode 100644
index 0000000..a430224
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/numberunboxing/InitializerNumberUnboxingTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2023, 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.numberunboxing;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InitializerNumberUnboxingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public InitializerNumberUnboxingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNumberUnboxing() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .addOptionsModification(opt -> opt.testing.enableNumberUnboxer = true)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::assertUnboxing)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(
+ "Main[-1;-1]", "Main[-2;-2]", "Main[1;1]", "Main[2;2]", "Main[3;4]");
+ }
+
+ private void assertUnboxing(CodeInspector codeInspector) {
+ ClassSubject mainClass = codeInspector.clazz(Main.class);
+ assertThat(mainClass, isPresent());
+
+ List<FoundMethodSubject> inits =
+ mainClass.allMethods(FoundMethodSubject::isInstanceInitializer);
+ assertEquals(3, inits.size());
+ inits.forEach(
+ m ->
+ assertTrue(
+ m.getParameters().stream().allMatch(p -> p.getTypeReference().isPrimitive())));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new Main(-1));
+ System.out.println(new Main(-2L));
+ System.out.println(new Main(1));
+ System.out.println(new Main(2L));
+ System.out.println(new Main(3, 4L));
+ }
+
+ private final int i;
+ private final long l;
+
+ @NeverInline
+ Main(Long l) {
+ this(l.intValue(), l);
+ }
+
+ @NeverInline
+ Main(Integer i) {
+ this(i, Long.valueOf(i));
+ }
+
+ @NeverInline
+ Main(Integer i, Long l) {
+ this.i = i;
+ this.l = l;
+ }
+
+ @Override
+ public String toString() {
+ return "Main[" + i + ";" + l + "]";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsOverrideNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsOverrideNumberUnboxingTest.java
new file mode 100644
index 0000000..67c4241
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsOverrideNumberUnboxingTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2023, 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.numberunboxing;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class VirtualMethodsOverrideNumberUnboxingTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public VirtualMethodsOverrideNumberUnboxingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNumberUnboxing() throws Throwable {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .addOptionsModification(opt -> opt.testing.enableNumberUnboxer = true)
+ .setMinApi(parameters)
+ .compile()
+ .inspect(this::assertUnboxing)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("3", "1", "43", "5", "0", "43");
+ }
+
+ private void assertUnboxed(MethodSubject methodSubject) {
+ assertThat(methodSubject, isPresent());
+ assertTrue(methodSubject.getProgramMethod().getParameter(0).isDoubleType());
+ assertTrue(methodSubject.getProgramMethod().getParameter(1).isIntType());
+ assertTrue(methodSubject.getProgramMethod().getReturnType().isLongType());
+ }
+
+ private void assertUnboxing(CodeInspector codeInspector) {
+ codeInspector.forAllClasses(c -> c.forAllVirtualMethods(this::assertUnboxed));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new Add().convert(1.3, 1) + 1L);
+ System.out.println(new Sub().convert(1.4, 2) + 1L);
+ System.out.println(new Cst().convert(1.4, 2) + 1L);
+ run(new Add());
+ run(new Sub());
+ run(new Cst());
+ }
+
+ @NeverInline
+ private static void run(Top top) {
+ System.out.println(top.convert(1.5, 3) + 1L);
+ }
+ }
+
+ @NeverClassInline
+ interface Top {
+ @NeverInline
+ Long convert(Double d, Integer i);
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class Add implements Top {
+ @Override
+ @NeverInline
+ public Long convert(Double d, Integer i) {
+ return Long.valueOf((long) (d.doubleValue() + i.intValue()));
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class Sub implements Top {
+ @Override
+ @NeverInline
+ public Long convert(Double d, Integer i) {
+ return Long.valueOf((long) (d.doubleValue() - i.intValue()));
+ }
+ }
+
+ @NeverClassInline
+ @NoHorizontalClassMerging
+ static class Cst implements Top {
+ @Override
+ @NeverInline
+ public Long convert(Double d, Integer i) {
+ return Long.valueOf(42L);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java
new file mode 100644
index 0000000..e3e6058
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b316744331;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.Tag;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Regress316744331Test extends DebugTestBase {
+
+ static final String EXPECTED = StringUtils.lines("No null fields");
+
+ static final int ENTRY_LINE = 23;
+ static final int IN_STREAM_CHECK_LINE = ENTRY_LINE + 1;
+ static final int IN_STREAM_IS_NULL_LINE = ENTRY_LINE + 2;
+ static final int OUT_STREAM_CHECK_LINE = ENTRY_LINE + 7;
+ static final int OUT_STREAM_IS_NULL_LINE = ENTRY_LINE + 8;
+ static final int NORMAL_EXIT_LINE = ENTRY_LINE + 11;
+
+ static final Value NULL_VALUE = Value.createObjectValue(Tag.OBJECT_TAG, 0);
+
+ private final TestParameters parameters;
+ private final MethodReference fooMethod;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDefaultCfRuntime()
+ .withDefaultDexRuntime()
+ .withAllApiLevels()
+ .build();
+ }
+
+ public Regress316744331Test(TestParameters parameters) {
+ this.parameters = parameters;
+ try {
+ this.fooMethod = Reference.methodFromMethod(Regress316744331TestClass.class.getMethod("foo"));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private TestBuilder<? extends SingleTestRunResult<?>, ?> getTestBuilder() {
+ return testForRuntime(parameters)
+ .addClasspathClasses(Regress316744331TestClass.class)
+ .addProgramClasses(Regress316744331TestClass.class);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ getTestBuilder()
+ .run(parameters.getRuntime(), Regress316744331TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testModifyInStreamField() throws Throwable {
+ runDebugTest(
+ getTestBuilder().debugConfig(parameters.getRuntime()),
+ Regress316744331TestClass.class,
+ breakpoint(fooMethod, ENTRY_LINE),
+ run(),
+ checkLine(ENTRY_LINE),
+ inspect(t -> assertEquals(NULL_VALUE, t.getFieldOnThis("m_instream", null))),
+ stepOver(),
+ checkLine(IN_STREAM_CHECK_LINE),
+ inspect(t -> assertNotEquals(NULL_VALUE, t.getFieldOnThis("m_instream", null))),
+ inspect(t -> t.setFieldOnThis("m_instream", null, NULL_VALUE)),
+ // Install a break point on the possible exits.
+ breakpoint(fooMethod, IN_STREAM_IS_NULL_LINE),
+ breakpoint(fooMethod, NORMAL_EXIT_LINE),
+ run(),
+ // TODO(b/316744331): D8 incorrectly optimizing out the code after the null check.
+ checkLine(parameters.isCfRuntime() ? IN_STREAM_IS_NULL_LINE : NORMAL_EXIT_LINE),
+ run());
+ }
+
+ @Test
+ public void testModifyOutStreamField() throws Throwable {
+ runDebugTest(
+ getTestBuilder().debugConfig(parameters.getRuntime()),
+ Regress316744331TestClass.class,
+ breakpoint(fooMethod, OUT_STREAM_CHECK_LINE),
+ run(),
+ checkLine(OUT_STREAM_CHECK_LINE),
+ inspect(t -> assertNotEquals(NULL_VALUE, t.getFieldOnThis("m_outstream", null))),
+ inspect(t -> t.setFieldOnThis("m_outstream", null, NULL_VALUE)),
+ // Install a break point on the possible exits.
+ breakpoint(fooMethod, OUT_STREAM_IS_NULL_LINE),
+ breakpoint(fooMethod, NORMAL_EXIT_LINE),
+ run(),
+ // TODO(b/316744331): D8 incorrectly optimizing out the code after the null check.
+ checkLine(parameters.isCfRuntime() ? OUT_STREAM_IS_NULL_LINE : NORMAL_EXIT_LINE),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331TestClass.java b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331TestClass.java
new file mode 100644
index 0000000..1a57586
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331TestClass.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b316744331;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class Regress316744331TestClass {
+
+ String filename;
+ FileReader m_instream;
+ PrintWriter m_outstream;
+
+ Regress316744331TestClass(String filename) {
+ this.filename = filename;
+ }
+
+ public void foo() throws IOException {
+ m_instream = new FileReader(filename);
+ if (null == m_instream) {
+ System.out.println("Reader is null!");
+ return;
+ }
+ m_outstream =
+ new PrintWriter(new java.io.BufferedWriter(new java.io.FileWriter(filename + ".java")));
+ if (null == m_outstream) {
+ System.out.println("Writer is null!");
+ return;
+ }
+ System.out.println("No null fields");
+ }
+
+ public static void main(String[] args) throws IOException {
+ // The debugger testing infra does not allow passing runtime arguments.
+ // Classpath should have an entry in normal and debugger runs so use it as the "file".
+ String cp = System.getProperty("java.class.path");
+ int jarIndex = cp.indexOf(".jar");
+ if (jarIndex < 0) {
+ jarIndex = cp.indexOf(".zip");
+ }
+ int start = cp.lastIndexOf(File.pathSeparatorChar, jarIndex);
+ String filename = cp.substring(Math.max(start, 0), jarIndex + 4);
+ new Regress316744331TestClass(filename).foo();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index 2917464..908da85 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -74,8 +74,6 @@
.addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters)
- .addOptionsModification(
- options -> options.testing.allowNonAbstractClassesWithAbstractMethods = true)
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
}
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 6c16dfd..d5e9fe3 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -74,8 +74,6 @@
.addProgramClassFileData(transformB())
.addKeepMainRule(Main.class)
.setMinApi(parameters)
- .addOptionsModification(
- options -> options.testing.allowNonAbstractClassesWithAbstractMethods = true)
.run(parameters.getRuntime(), Main.class)
.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
}
diff --git a/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java b/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java
index 09ca4e0..c5eaf59 100644
--- a/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java
+++ b/src/test/java/com/android/tools/r8/retrace/InvalidMappingRangesB309080420Test.java
@@ -36,7 +36,9 @@
" 11:2:void a() -> a", // Unexpected line range [11:2] - interpreting as [2:11]
" 12:21:void a(android.content.Intent) -> a",
// Allow identifier content to follow <init>/<clinit>.
- " 22:41:void <clinit>$more$stuff() -> clinit$move$stuff");
+ " 22:41:void <clinit>$more$stuff() -> clinit$move$stuff",
+ // Allow type identifiers to start with '.'
+ ".Foo -> o.bar:");
@Test
public void test() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index faba9a5..c0211ef 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -233,7 +233,7 @@
.apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
.compile()
.graphInspector();
- assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector, true, true);
+ assertRetainedClassesEqual(referenceInspector, ifThenKeepClassMembersInspector);
GraphInspector ifThenKeepClassesWithMembersInspector =
testForR8(Backend.CF)
@@ -252,8 +252,7 @@
.apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
.compile()
.graphInspector();
- assertRetainedClassesEqual(
- referenceInspector, ifThenKeepClassesWithMembersInspector, true, true);
+ assertRetainedClassesEqual(referenceInspector, ifThenKeepClassesWithMembersInspector);
GraphInspector ifHasMemberThenKeepClassInspector =
testForR8(Backend.CF)
@@ -274,7 +273,7 @@
.apply(this::suppressZipFileAssignmentsToJavaLangAutoCloseable)
.compile()
.graphInspector();
- assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector, true, true);
+ assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
}
private void configureHorizontalClassMerging(R8FullTestBuilder testBuilder) {
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/AllowHorizontalClassMergingWithIfRuleTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/AllowHorizontalClassMergingWithIfRuleTest.java
new file mode 100644
index 0000000..af28109
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/AllowHorizontalClassMergingWithIfRuleTest.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2023, 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.ifrule;
+
+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 AllowHorizontalClassMergingWithIfRuleTest 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("-if class * { void foo(); } -keep class " + Main.class.getTypeName())
+ .addHorizontallyMergedClassesInspector(
+ inspector ->
+ inspector.assertIsCompleteMergeGroup(B.class, C.class).assertNoOtherClassesMerged())
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello, world!");
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ Object aOrBOrC =
+ System.currentTimeMillis() > 0
+ ? new A()
+ : System.currentTimeMillis() > 1 ? new B() : new C();
+ System.out.print(aOrBOrC);
+ A.foo();
+ }
+ }
+
+ static class A {
+
+ static void foo() {
+ System.out.println();
+ }
+
+ @Override
+ public String toString() {
+ return "Hello, world!";
+ }
+ }
+
+ static class B {}
+
+ static class C {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/PinningStarPatternTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/PinningStarPatternTest.java
new file mode 100644
index 0000000..d65f8bd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/PinningStarPatternTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2023, 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.ifrule;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import 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.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class PinningStarPatternTest extends TestBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultDexRuntime().build();
+ }
+
+ public static final List<Class<?>> EXPECTED_ABSENT = ImmutableList.of(A.class);
+ public static final List<Class<?>> EXPECTED_PRESENT = ImmutableList.of(TestClass.class, B.class);
+
+ private TestParameters parameters;
+
+ public PinningStarPatternTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNoKeep() throws Exception {
+ testKeepRule("", ImmutableList.of(A.class, B.class), ImmutableList.of(TestClass.class));
+ }
+
+ @Test
+ public void testNoIf() throws Exception {
+ testKeepRule("-keep class **B { *; }", EXPECTED_ABSENT, EXPECTED_PRESENT);
+ }
+
+ @Test
+ public void testR8IfStar() throws Exception {
+ testKeepRule("-if class * -keep class **B { *; }", EXPECTED_ABSENT, EXPECTED_PRESENT);
+ }
+
+ @Test
+ public void testR8IfStarWithMethod() throws Exception {
+ testKeepRule("-if class * { z(); } -keep class **B { *; }", EXPECTED_ABSENT, EXPECTED_PRESENT);
+ }
+
+ @Test
+ public void testR8IfStarWithEmptyMethod() throws Exception {
+ // We should also not keep B, since y() is dead (empty).
+ testKeepRule("-if class * { y(); } -keep class **B { *; }", EXPECTED_ABSENT, EXPECTED_PRESENT);
+ }
+
+ @Test
+ public void testR8IfStarField() throws Exception {
+ // We should also not keep B, since the only_read field is dead.
+ testKeepRule(
+ "-if class * { int only_read; } -keep class **B { *; }", EXPECTED_ABSENT, EXPECTED_PRESENT);
+ }
+
+ private void testKeepRule(String keepRule, List<Class<?>> absent, List<Class<?>> present)
+ throws IOException, ExecutionException, CompilationFailedException {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, TestClass.class)
+ .addKeepRules(keepRule)
+ .addKeepMainRule(TestClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("42", "0")
+ .inspect(
+ inspector -> {
+ absent.forEach(clazz -> assertThat(inspector.clazz(clazz), isAbsent()));
+ present.forEach(clazz -> assertThat(inspector.clazz(clazz), isPresent()));
+ });
+ }
+
+ static class A {
+ boolean use = true;
+
+ public A() {
+ if (System.currentTimeMillis() == 0) {
+ use = false;
+ }
+ }
+
+ public void bar() {
+ if (use) {
+ System.out.println(42);
+ }
+ }
+ }
+
+ static class B {
+ public static void foo() {
+ System.out.println(0);
+ }
+ }
+
+ static class TestClass {
+
+ public static int only_read = 88;
+
+ public void z() {
+ if (System.currentTimeMillis() == 0) {
+ System.out.println("foobar");
+ }
+ }
+
+ public void y() {}
+
+ public static void main(String[] args) {
+ new TestClass().z();
+ new TestClass().y();
+ int a = TestClass.only_read;
+ new A().bar();
+ B.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
index 01f8190..aa46e96 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@@ -114,7 +114,7 @@
if (enableVerticalClassMerging) {
// Verify that SuperTestClass has been merged into TestClass.
- assertThat(inspector.clazz(SuperTestClass.class), not(isPresent()));
+ assertThat(inspector.clazz(SuperTestClass.class), isAbsent());
assertEquals(
"java.lang.Object", testClassSubject.getDexProgramClass().superType.toSourceString());
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
index ed578cd..9f7bb91 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
@@ -4,9 +4,9 @@
package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.NoAccessModification;
@@ -97,8 +97,8 @@
// Verify that A and I are no longer present when vertical class merging is enabled.
if (enableVerticalClassMerging) {
- assertThat(inspector.clazz(A.class), not(isPresent()));
- assertThat(inspector.clazz(I.class), not(isPresent()));
+ assertThat(inspector.clazz(A.class), isAbsent());
+ assertThat(inspector.clazz(I.class), isAbsent());
}
}
diff --git a/third_party/dependencies_plugin.tar.gz.sha1 b/third_party/dependencies_plugin.tar.gz.sha1
index ba75d12..f85351e 100644
--- a/third_party/dependencies_plugin.tar.gz.sha1
+++ b/third_party/dependencies_plugin.tar.gz.sha1
@@ -1 +1 @@
-369870d45ae8721ad1379b8bf108e107ad1b5175
\ No newline at end of file
+a58a2edb72ff3a3126e4846057dd55eb0e7c27f8
\ No newline at end of file
diff --git a/third_party/kotlin/kotlin-compiler-1.9.21.tar.gz.sha1 b/third_party/kotlin/kotlin-compiler-1.9.21.tar.gz.sha1
new file mode 100644
index 0000000..8e93ad6
--- /dev/null
+++ b/third_party/kotlin/kotlin-compiler-1.9.21.tar.gz.sha1
@@ -0,0 +1 @@
+17c6c5c9cefab179553adaaf860c86e4e598a002
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 3abe651..cc12d2a 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -477,7 +477,7 @@
if line.lstrip().startswith('#'):
return False
if ('-injars' in line or '-libraryjars' in line or '-print' in line or
- '-applymapping' in line):
+ '-applymapping' in line or '-tracing' in line):
return True
if minify == 'force-enable' and '-dontobfuscate' in line:
return True
diff --git a/tools/create_local_maven_with_dependencies.py b/tools/create_local_maven_with_dependencies.py
index 05abfb5..e2f0366 100755
--- a/tools/create_local_maven_with_dependencies.py
+++ b/tools/create_local_maven_with_dependencies.py
@@ -101,11 +101,7 @@
'org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:4.1.0',
'org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.9.10',
'net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:3.0.1',
-
- # Patched version of org.spdx.sbom:org.spdx.sbom.gradle.plugin:0.4.0.
- # See
- # https://github.com/spdx/spdx-gradle-plugin/issues/69#issuecomment-1799122543.
- 'org.spdx.sbom:org.spdx.sbom.gradle.plugin:0.4.0-r8-patch02',
+ 'org.spdx.sbom:org.spdx.sbom.gradle.plugin:0.4.0',
# See https://github.com/FasterXML/jackson-core/issues/999.
'ch.randelshofer:fastdoubleparser:0.8.0',
]