Merge commit 'b6c3b24b' into dev-release Change-Id: I6edc62a72fe3ade68162eb482185a22b5acd8044
diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..a2271c0 --- /dev/null +++ b/BUILD.md
@@ -0,0 +1,214 @@ +# R8 Build System Architecture & Overview + +This document provides an overview of the build architecture. + +--- + +## Build Design + +The build setup follows these core principles: + +- **Hermetic:** Builds only download against Google resources (located in + [`third_party/`](./third_party)). +- **Layered Tooling:** + - **Python Drivers** ([`tools/gradle.py`](./tools/gradle.py), + [`tools/test.py`](./tools/test.py), etc.) ensure pre-builts (JDKs, + Gradle binaries, local Maven caches) from Google Cloud Storage (GCS) + before invoking Gradle. + - **Gradle Project** (located under [`d8_r8/`](./d8_r8)) coordinates the + actual build. +- **Bootstrapping:** The official compiler jar is compiled with R8. +- **Testing what you release:** Tests run against the official bootstrapped and + optimized [`r8lib.jar`](./build/libs/r8lib.jar) release artifact rather than a + debug build. + +--- + +## The Build Process + +``` ++---------------------------------------------------------------------------------------+ +| R8 COMPILER SUITE | ++---------------------------------------------------------------------------------------+ + + [main] ───────────────────────────> threading-module-blocking.jar ───────┐ + [main] ───────────────────────────> threading-module-single-threaded.jar ┼──> deps.jar + [third_party], [resourceshrinker] ───────────────────────────────────────┘ │ + │ + [keepradius], [libanalyzer] ────────────────────────────────────────> proto.jar │ + │ │ + [main], [keepanno], [resourceshrinker], │ │ + [keepradius], [libanalyzer], [assistant] ──┬──> r8-src.jar │ │ + │ │ │ + └──> r8-full-exclude-deps.jar │ │ + r8lib-exclude-deps.jar <──────┘ │ │ │ + ▲ ▼ │ │ + │ r8.jar <─────────────┴───────┘ + │ / \ + │ ▼ ▼ + [tests] ───────────────> test-references ─--> r8lib.jar processkeepruleslib.jar + │ + ▼ + r8lib.zip + ++---------------------------------------------------------------------------------------+ +| KEEP ANNOTATIONS TOOLS | ++---------------------------------------------------------------------------------------+ + + [keepanno] ──┬──> keepanno-annotations.jar + ├──> keepanno-annotations-legacy.jar + ├──> keepanno-annotations-androidx.jar + └──> keepanno-tools.jar ──> keepanno-toolslib.jar + ++---------------------------------------------------------------------------------------+ +| DESUGAR CONFIGURATION | ++---------------------------------------------------------------------------------------+ + + [library_desugar] ──> desugar_jdk_libs_configuration.zip +``` + +### R8 Compiler Suite + +- [`r8lib.zip`](./build/libs/r8lib.zip): + - Released: To GMaven for distribution to AGP and other build systems. + - What: The Maven repository archive for the compiler. + - How: Package [`r8lib.jar`](./build/libs/r8lib.jar) with POM and checksum + files in a Maven repository layout. +- [`r8lib.jar`](./build/libs/r8lib.jar): + - Released: The primary production compiler JAR. + - What: The relocated, standalone, optimized compiler JAR. + - How: [`r8.jar`](./build/libs/r8.jar) is compiled and optimized with R8 with + respect to declared keep rules and dynamic keep rules from `test-references`. +- [`r8.jar`](./build/libs/r8.jar): + - Released: The non-optimized production compiler JAR. + - What: The relocated, standalone compiler JAR. + - How: [`r8-full-exclude-deps.jar`](./build/libs/r8-full-exclude-deps.jar) + and [`proto.jar`](./build/libs/proto.jar) are bundled together, along with + dependencies in [`deps.jar`](./build/libs/deps.jar) relocated under + `com.android.tools.r8.*`. +- [`r8lib-exclude-deps.jar`](./build/libs/r8lib-exclude-deps.jar): + - Released: Published for build systems (such as internal Google repo) + that manage and provide their own dependencies on the classpath. + - What: The optimized compiler JAR without dependencies (note the special + proto and threading modules (part of [`deps.jar`](./build/libs/deps.jar)) + that are not generic dependencies). + - How: [`r8-full-exclude-deps.jar`](./build/libs/r8-full-exclude-deps.jar) + is compiled and optimized with R8 with respect to declared keep rules and + dynamic keep rules from `test-references`, using + [`deps.jar`](./build/libs/deps.jar) and + [`proto.jar`](./build/libs/proto.jar) as classpath. +- `test-references`: + - What: Dynamically generated keep rules extracted from test references. + - How: Trace compiler references from the test suite to preserve compiler + internals accessed during testing. +- [`r8-full-exclude-deps.jar`](./build/libs/r8-full-exclude-deps.jar): + - Released: Published as an unoptimized compiler JAR without third-party + dependencies for custom downstream packaging. + - What: The compiler JAR without dependencies. + - How: Compile classes from + [`main`](./d8_r8/main), + [`keepanno`](./d8_r8/keepanno), + [`resourceshrinker`](./d8_r8/resourceshrinker), + [`keepradius`](./d8_r8/keepradius), + [`libanalyzer`](./d8_r8/libanalyzer), + and [`assistant`](./d8_r8/assistant). +- [`r8-src.jar`](./build/libs/r8-src.jar): + - Released: Published to provide source code for IDE navigation, debugging, + and open-source compliance. + - What: The compiler sources JAR. + - How: Package sources from + [`main`](./d8_r8/main), + [`keepanno`](./d8_r8/keepanno), + [`resourceshrinker`](./d8_r8/resourceshrinker), + [`keepradius`](./d8_r8/keepradius), + [`libanalyzer`](./d8_r8/libanalyzer), + and [`assistant`](./d8_r8/assistant). +- [`deps.jar`](./build/libs/deps.jar): + - What: The JAR containing third-party dependencies, resource shrinker + dependencies, threading modules, and consolidated licensing. + - How: Runtime dependencies from [`third_party/`](./third_party), + resource shrinker dependencies from + [`resourceshrinker`](./d8_r8/resourceshrinker), + [`threading-module-blocking.jar`](./build/libs/threading-module-blocking.jar), + [`threading-module-single-threaded.jar`](./build/libs/threading-module-single-threaded.jar), + and the consolidated `LICENSE` file are merged together. +- [`proto.jar`](./build/libs/proto.jar): + - What: The JAR containing compiled protobuf classes used for keep rule + radius and library analysis data models. These are only needed for optional + analysis features. Users without this dependency must compile the published + `.proto` schemas directly using their own toolchains. + - How: Protobuf classes from [`keepradius`](./d8_r8/keepradius) and + [`libanalyzer`](./d8_r8/libanalyzer) are bundled together. +- [`threading-module-blocking.jar`](./build/libs/threading-module-blocking.jar): + - Released: Published as an optional runtime module providing multi-threaded + execution for modular classpath environments. + - What: The multi-threaded execution provider. It is packaged separately so + the concurrency model can be controlled via classpath. This is useful to + have a compiler without code even mentioning blocking code (for aggressive + code checkers). + - How: Package blocking provider classes from [`main`](./d8_r8/main). +- [`threading-module-single-threaded.jar`](./build/libs/threading-module-single-threaded.jar): + - Released: Published as an optional runtime module providing single-threaded + execution for environments that prohibit blocking code. + - What: The single-threaded execution provider. (see + [`threading-module-blocking.jar`](./build/libs/threading-module-blocking.jar)) + - How: Package single-threaded provider classes from [`main`](./d8_r8/main). +- [`processkeepruleslib.jar`](./build/libs/processkeepruleslib.jar): + - Released: Published as a standalone optimized tool for parsing, evaluating, + and filtering ProGuard keep rules. + - What: The standalone, optimized tool JAR used to process and filter ProGuard + keep rules. + - How: [`r8.jar`](./build/libs/r8.jar) is compiled and optimized with R8 using + `src/main/keep_processkeeprules.txt`. + +### Keep Annotations Tools + +- [`keepanno-toolslib.jar`](./build/libs/keepanno-toolslib.jar): + - Released: Published as a standalone, optimized library for processing keep + annotations in build pipelines. + - What: The relocated, standalone, optimized keep-annotation tools JAR. + - How: [`keepanno-tools.jar`](./build/libs/keepanno-tools.jar) is compiled and + optimized with R8 with respect to keep rules, using unrelocated ASM on the + classpath. +- [`keepanno-tools.jar`](./build/libs/keepanno-tools.jar): + - Released: Published as an unminified standalone JAR with relocated + dependencies for keep-annotation processing and debugging. + - What: The standalone JAR containing tools for processing keep annotations + with non-ASM dependencies relocated under + `com.android.tools.r8.keepanno.*`. ASM is kept external as it is used as the + public API interface. + - How: Compiled classes from [`keepanno`](./d8_r8/keepanno) and its non-ASM + dependencies are bundled together, with dependencies relocated under + `com.android.tools.r8.keepanno.*` (leaving ASM external for public API + integration). +- [`keepanno-annotations.jar`](./build/libs/keepanno-annotations.jar): + - Released: Published as the compile-time annotations library for source + code declaring keep annotations in + `com.android.tools.r8.keepanno.annotations.*`. + - What: The annotation library containing keep-annotation definitions + (`com.android.tools.r8.keepanno.annotations.*`). + - How: Package compiled annotation classes from [`keepanno`](./d8_r8/keepanno). +- [`keepanno-annotations-legacy.jar`](./build/libs/keepanno-annotations-legacy.jar): + - Released: Published for backward compatibility with projects using legacy + keep-annotation package namespaces. + - What: The legacy package variant of the keep-annotations library. + - How: Package compiled legacy annotation classes from + [`keepanno`](./d8_r8/keepanno). +- [`keepanno-annotations-androidx.jar`](./build/libs/keepanno-annotations-androidx.jar): + - Released: Published for AndroidX projects using the + `androidx.annotation.keep.*` package namespace. + - What: The AndroidX package variant (`androidx.annotation.keep.*`) of the + keep-annotations library. + - How: Package compiled AndroidX keep-annotation classes from + [`keepanno`](./d8_r8/keepanno). + +### Desugar Configuration + +- [`desugar_jdk_libs_configuration.zip`](./build/libs/desugar_jdk_libs_configuration.zip): + - Released: Published to GMaven to configure Java standard library desugaring + in D8 and R8 (along with JDK 11, minimal, and NIO variants). + - What: The Maven repository archive containing the configuration JAR and POM. + - How: Package the desugar specification JSON, generated lint metadata, and + compiled conversion classes from + [`library_desugar`](./d8_r8/library_desugar) with POM and checksum files in + a Maven repository layout.
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/CreateArtTestsTask.kt b/d8_r8/commonBuildSrc/src/main/kotlin/CreateArtTestsTask.kt new file mode 100644 index 0000000..e6a57ca --- /dev/null +++ b/d8_r8/commonBuildSrc/src/main/kotlin/CreateArtTestsTask.kt
@@ -0,0 +1,90 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import java.io.File +import kotlin.io.path.listDirectoryEntries +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction + +@CacheableTask +public abstract class CreateArtTestsTask : DefaultTask() { + @get:[InputFiles PathSensitive(PathSensitivity.RELATIVE)] + public abstract val artTestCaseDirectory: DirectoryProperty + + @get:OutputDirectory public abstract val outputDir: DirectoryProperty + + @TaskAction + public fun createTests() { + val outputDirectory = outputDir.get().asFile + outputDirectory.deleteRecursively() + val artTestCaseSubdirectory = File(artTestCaseDirectory.get().asFile, "dx") + check(artTestCaseSubdirectory.exists()) + val subDirectories = artTestCaseSubdirectory.toPath().listDirectoryEntries() + val testCases = subDirectories.map { it.fileName.toString() } + for ((tool, toolEnum) in + listOf("r8" to "R8", "d8" to "R8_AFTER_D8", "r8cf" to "D8_AFTER_R8CF")) { + val packageDirectory = File(outputDirectory, "com/android/tools/r8/art/none/$tool") + packageDirectory.mkdirs() + for (testCase in testCases) { + val className = "Art${testCase.replace("-", "_")}Test" + val testRuntimes = + if (testCase == "800-smali") + "withDexRuntimesEndingAtExcluding(\n com.android.tools.r8.ToolHelper.DexVm.Version.V17_0_0)" + else "withDexRuntimes()" + File(packageDirectory, "$className.java") + .writeText( + """// 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 com.android.tools.r8.art.none.$tool; + +import static com.android.tools.r8.R8RunArtTestsTest.DexTool.NONE; + +import com.android.tools.r8.R8RunArtTestsTest; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.DexVm; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +/** + * Auto-generated test for the art $testCase test using the none toolchain. + * + * DO NOT EDIT THIS FILE. EDIT THE HERE DOCUMENT TEMPLATE IN d8_r8/commonBuildSrc/src/main/kotlin/CreateArtTestsTask.kt INSTEAD! + */ +@RunWith(Parameterized.class) +public class $className extends R8RunArtTestsTest { + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return TestBase.getTestParameters().$testRuntimes.build(); + } + + private final TestParameters parameters; + + public $className(TestParameters parameters) { + super("$testCase", NONE); + this.parameters = parameters; + } + + @Test + public void test() throws Throwable { + runArtTest(parameters.getRuntime().asDex().getVm(), CompilerUnderTest.$toolEnum); + } +} +""" + ) + } + } + } +}
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/CreateR8LibraryTask.kt b/d8_r8/commonBuildSrc/src/main/kotlin/CreateR8LibraryTask.kt index e9c96d3..ebf0c68 100644 --- a/d8_r8/commonBuildSrc/src/main/kotlin/CreateR8LibraryTask.kt +++ b/d8_r8/commonBuildSrc/src/main/kotlin/CreateR8LibraryTask.kt
@@ -135,14 +135,16 @@ } args = myArgs } - if (!enableKeepAnnotations.get()) { // delete api database if we do not enable keep annotations + if (!enableKeepAnnotations.get()) { // delete api resources if we do not enable keep annotations // The URI must be prefixed with "jar:" to tell the FileSystem provider to treat it as a // ZIP/JAR val uri = create("jar:${outputJarFile.toPath().toUri()}") val env = mapOf("create" to "false") newFileSystem(uri, env).use { zipfs -> - val pathInZip = zipfs.getPath("resources/api_database.ser") - deleteIfExists(pathInZip) + deleteIfExists(zipfs.getPath("resources/api_database.ser")) + deleteIfExists(zipfs.getPath("resources/missing.api.txt")) + deleteIfExists(zipfs.getPath("resources/hidden.api.txt")) + deleteIfExists(zipfs.getPath("resources/hidden.jar.txt")) } } if (replaceInOutputJar.isPresent) {
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt index c6333f0..cb899e3 100644 --- a/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt +++ b/d8_r8/commonBuildSrc/src/main/kotlin/DependenciesPlugin.kt
@@ -10,7 +10,6 @@ import kotlin.reflect.full.declaredMemberProperties import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.Task import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.Directory import org.gradle.api.file.RegularFile @@ -135,12 +134,6 @@ return files(project.getRoot().resolve(thirdPartyDependency.path).resolveAll(*paths)) } -public fun Task.dependOnPythonScripts() { - // There is no easy way to track transitive python dependencies, so add all python files. - val toolsDir = project.fileTree(project.getRoot().resolve("tools")).exclude("linux/**") - inputs.files(toolsDir.include("**/*.py")) -} - public fun File.resolveAll(vararg xs: String): File { var that = this for (x in xs) {
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/R8ConventionPlugin.kt b/d8_r8/commonBuildSrc/src/main/kotlin/R8ConventionPlugin.kt index 96d4131..41c150c 100644 --- a/d8_r8/commonBuildSrc/src/main/kotlin/R8ConventionPlugin.kt +++ b/d8_r8/commonBuildSrc/src/main/kotlin/R8ConventionPlugin.kt
@@ -27,7 +27,7 @@ is JavaPlugin -> { target.extensions.getByType<JavaPluginExtension>().apply { // Compile using newer compiler for performance and other improvements - toolchain { languageVersion.set(JavaLanguageVersion.of(21)) } + toolchain { languageVersion.set(JavaLanguageVersion.of(25)) } withSourcesJar() } target.tasks.withType<JavaCompile>().configureEach {
diff --git a/d8_r8/main/build.gradle.kts b/d8_r8/main/build.gradle.kts index 4550de3..21ca265 100644 --- a/d8_r8/main/build.gradle.kts +++ b/d8_r8/main/build.gradle.kts
@@ -174,7 +174,10 @@ val srcDir = getRoot().resolveAll("src", "main", "java") main { - resources.srcDirs(getRoot().resolveAll("third_party", "api_database", "api_database")) + resources.srcDirs( + getRoot().resolveAll("third_party", "api_database", "api_database"), + getRoot().resolveAll("src", "main", "resources"), + ) java { srcDir(srcDir) if (turboState != null && !turboState.paths.isEmpty()) {
diff --git a/d8_r8/test_modules/tests_java_8/build.gradle.kts b/d8_r8/test_modules/tests_java_8/build.gradle.kts index 53f6fde..7239241 100644 --- a/d8_r8/test_modules/tests_java_8/build.gradle.kts +++ b/d8_r8/test_modules/tests_java_8/build.gradle.kts
@@ -17,13 +17,7 @@ configurations.resolvable("partialTestClassesConfig") { extendsFrom(partialTestClassesScope) } java { - sourceSets.test.configure { - java { - srcDir(root.resolveAll("src", "test", "java")) - // Generated art tests - srcDir(root.resolveAll("build", "generated", "test", "java")) - } - } + sourceSets.test.configure { java { srcDir(root.resolveAll("src", "test", "java")) } } // We are using a new JDK to compile to an older language version, as we don't have JDK-8 for // Windows in our repo. sourceCompatibility = JavaVersion.VERSION_1_8 @@ -102,34 +96,20 @@ } tasks { - getByName<Delete>("clean") { - // TODO(b/327315907): Don't generating into the root build dir. - delete.add( - getRoot() - .resolveAll("build", "generated", "test", "java", "com", "android", "tools", "r8", "art") - ) - } - val createArtTests = - register<Exec>("createArtTests") { - dependsOn(sharedDepsConfig) - dependOnPythonScripts() - // TODO(b/327315907): Don't generating into the root build dir. - val outputDir = - getRoot() - .resolveAll("build", "generated", "test", "java", "com", "android", "tools", "r8", "art") - val createArtTestsScript = getRoot().resolveAll("tools", "create_art_tests.py") - inputs.dir(getRoot().resolveAll("tests", "2017-10-04")) - outputs.dir(outputDir) - workingDir(getRoot()) - commandLine("python3", createArtTestsScript) + register<CreateArtTestsTask>("createArtTests") { + artTestCaseDirectory = + project(":third_party").tasks.named<DownloadDependency>("download_art-tests").flatMap { + it.outputDir + } + outputDir = layout.buildDirectory.dir("generated/test/java") } + java.sourceSets.test.configure { java.srcDir(createArtTests) } "compileTestJava" { dependsOn(sharedDepsConfig) dependsOn(":testbase:compileJava") } withType<JavaCompile> { - dependsOn(createArtTests) dependsOn(sharedDepsConfig) dependsOn(":testbase:compileJava") } @@ -160,6 +140,13 @@ include("$r8/desugaring/interfacemethods/methodparameters/I.java") } } + + register<JavaExec>("generateApiAmendments") { + dependsOn(partialTestClassesConfig) + mainClass.set("com.android.tools.r8.androidapi.GenerateApiAmendments") + classpath = sourceSets.test.get().runtimeClasspath + partialTestClassesConfig + // The output is deliberately not Gradle tracked since its a circular dependency. + } } val testJar by configurations.consumable("testJar")
diff --git a/infra/config/global/generated/commit-queue.cfg b/infra/config/global/generated/commit-queue.cfg index 19b5ed9..b03dadb 100644 --- a/infra/config/global/generated/commit-queue.cfg +++ b/infra/config/global/generated/commit-queue.cfg
@@ -31,4 +31,31 @@ } } } + post_actions { + name: "presubmit-verification-success" + conditions { + mode: "DRY_RUN" + statuses: SUCCEEDED + } + vote_gerrit_labels { + votes { + name: "Presubmit-Verified" + value: 1 + } + } + } + post_actions { + name: "presubmit-verification-failure" + conditions { + mode: "DRY_RUN" + statuses: FAILED + statuses: CANCELLED + } + vote_gerrit_labels { + votes { + name: "Presubmit-Verified" + value: -1 + } + } + } }
diff --git a/infra/config/global/generated/project.cfg b/infra/config/global/generated/project.cfg index 70ba83c..63e9dd9 100644 --- a/infra/config/global/generated/project.cfg +++ b/infra/config/global/generated/project.cfg
@@ -7,7 +7,7 @@ name: "r8" access: "group:all" lucicfg { - version: "1.46.3" + version: "1.46.6" package_dir: ".." config_dir: "generated" entry_point: "main.star"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star index b69d7ec..5098efd 100755 --- a/infra/config/global/main.star +++ b/infra/config/global/main.star
@@ -190,13 +190,40 @@ name = "main-cq", watch = cq.refset( repo = "https://r8.googlesource.com/r8", - refs = ["refs/heads/.+"] + refs = ["refs/heads/.+"], ), retry_config = cq.RETRY_TRANSIENT_FAILURES, acls = [ acl.entry(acl.CQ_COMMITTER, groups = ["project-r8-committers"]), acl.entry(acl.CQ_DRY_RUNNER, groups = ["googlers"]), ], + post_actions = [ + cq.post_action_gerrit_label_votes( + name = "presubmit-verification-success", + conditions = [ + cq.post_action_triggering_condition( + mode = cq.MODE_DRY_RUN, + statuses = [cq.STATUS_SUCCEEDED], + ), + ], + labels = { + "Presubmit-Verified": 1, + }, + ), + cq.post_action_gerrit_label_votes( + name = "presubmit-verification-failure", + conditions = [ + cq.post_action_triggering_condition( + mode = cq.MODE_DRY_RUN, + statuses = [cq.STATUS_FAILED, cq.STATUS_CANCELLED], + ), + ], + labels = { + # set it with -1 to indicate that it failed (or cancelled). + "Presubmit-Verified": -1, + }, + ), + ], ) luci.list_view( @@ -252,7 +279,7 @@ elif archive: dimensions["archive"] = "true" elif tester: - dimensions["tester"] = "true" + dimensions["tester"] = "true" else: dimensions["normal"] = "true" return dimensions
diff --git a/src/main/java/com/android/tools/r8/ApiDatabaseGenerator.java b/src/main/java/com/android/tools/r8/ApiDatabaseGenerator.java index 7a9c6e3..13d5898 100644 --- a/src/main/java/com/android/tools/r8/ApiDatabaseGenerator.java +++ b/src/main/java/com/android/tools/r8/ApiDatabaseGenerator.java
@@ -3,7 +3,7 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8; -import com.android.tools.r8.androidapi.ApiDatabaseEntry; +import com.android.tools.r8.apimodel.AmendApiFromResources; import com.android.tools.r8.apimodel.AndroidApiHashingDatabaseBuilderGenerator; import com.android.tools.r8.apimodel.AndroidApiHashingDatabaseBuilderGenerator.GenerationException; import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser; @@ -11,22 +11,24 @@ import com.android.tools.r8.apimodel.ParsedApiClass; import com.android.tools.r8.apimodel.ParsedApiClassFlattening; import com.android.tools.r8.apimodel.ParsedApiClassMerging; +import com.android.tools.r8.apimodel.ParsedApiClassSorting; import com.android.tools.r8.apimodel.ParsedApiClassTrimming; import com.android.tools.r8.apimodel.ParsedApiClassTrimming.JarTrimmer; import com.android.tools.r8.apimodel.ParsedApiClassTrimming.RemovedTrimmer; +import com.android.tools.r8.apimodel.ParsedApiClassTrimming.Trimmer; import com.android.tools.r8.apimodel.ParsedApiClassVerifier; import com.android.tools.r8.apimodel.jar.ApiJarInfo; +import com.android.tools.r8.apimodel.jar.ApiJarMerging; import com.android.tools.r8.apimodel.jar.ApiJarReader; import com.android.tools.r8.keepanno.annotations.KeepForApi; import com.android.tools.r8.origin.CommandLineOrigin; -import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.ExceptionUtils; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Map; +import java.util.function.Function; @KeepForApi public class ApiDatabaseGenerator { @@ -49,22 +51,59 @@ private static void runInternal(ApiDatabaseGeneratorCommand command) throws ApiDatabaseGeneratorException { try { - Collection<ParsedApiClass> classes = extractClasses(command); - ApiJarInfo jarInfo = ApiJarReader.read(command.getJarPaths()); - classes = ParsedApiClassMerging.merge(classes, command.getDiagnosticsHandler()); - classes = ParsedApiClassFlattening.flatten(classes); - classes = ParsedApiClassTrimming.trim(classes, new RemovedTrimmer()); - classes = ParsedApiClassTrimming.trim(classes, new JarTrimmer(jarInfo)); - ParsedApiClassVerifier.verify(classes); - Map<ApiDatabaseEntry, AndroidApiLevel> databaseEntries = - AndroidApiHashingDatabaseBuilderGenerator.generateEntries(classes); - AndroidApiHashingDatabaseBuilderGenerator.writeEntries( - databaseEntries, command.getOutputPath()); - } catch (ParsingException | GenerationException | IOException e) { + var apiClasses = generateClasses(command, trimmer -> trimmer); + var entries = AndroidApiHashingDatabaseBuilderGenerator.generateEntries(apiClasses); + AndroidApiHashingDatabaseBuilderGenerator.writeEntries(entries, command.getOutputPath()); + } catch (GenerationException e) { throw new ApiDatabaseGeneratorException("Failed to generate API database", e); } } + static <E extends Throwable> Collection<ParsedApiClass> generateClasses( + ApiDatabaseGeneratorCommand command, Function<JarTrimmer, Trimmer<E>> jarTrimmerWrapper) + throws ApiDatabaseGeneratorException, E { + try { + Collection<ParsedApiClass> classes = extractClasses(command); + classes = ParsedApiClassMerging.merge(classes, command.getDiagnosticsHandler()); + + List<ApiJarInfo> jarInfos = extractJars(command); + ApiJarInfo jarInfo = ApiJarMerging.mergeJarInfos(jarInfos); + + if (command.shouldAmend()) { + amendApiData(classes, jarInfo); + } + classes = flattenHierarchy(classes); + classes = ParsedApiClassTrimming.trim(classes, new RemovedTrimmer()); + Trimmer<E> jarTrimmer = jarTrimmerWrapper.apply(new JarTrimmer(jarInfo)); + classes = ParsedApiClassTrimming.trim(classes, jarTrimmer); + classes = ParsedApiClassSorting.sorted(classes); + ParsedApiClassVerifier.verify(classes); + return classes; + } catch (ParsingException | IOException e) { + throw new ApiDatabaseGeneratorException("Failed to generate API classes", e); + } + } + + private static Collection<ParsedApiClass> flattenHierarchy(Collection<ParsedApiClass> classes) + throws ApiDatabaseGeneratorException { + try { + return ParsedApiClassFlattening.flatten(classes); + } catch (ApiDatabaseGeneratorException e) { + throw new ApiDatabaseGeneratorException("Could not flatten class hierarchy", e); + } + } + + private static void amendApiData(Collection<ParsedApiClass> classes, ApiJarInfo jarInfo) + throws ApiDatabaseGeneratorException, IOException { + try { + AmendApiFromResources.applyAmendments(classes, jarInfo); + } catch (ApiDatabaseGeneratorException e) { + throw new ApiDatabaseGeneratorException("Database amendment failed", e); + } catch (IOException e) { + throw new IOException("Database amendment failed", e); + } + } + private static List<ParsedApiClass> extractClasses(ApiDatabaseGeneratorCommand command) throws ParsingException { List<ParsedApiClass> allParsed = new ArrayList<>(); @@ -75,6 +114,15 @@ return allParsed; } + private static List<ApiJarInfo> extractJars(ApiDatabaseGeneratorCommand command) + throws ApiDatabaseGeneratorException, IOException { + List<ApiJarInfo> allRead = new ArrayList<>(); + for (Path jarPath : command.getJarPaths()) { + allRead.add(ApiJarReader.read(jarPath)); + } + return allRead; + } + public static void main(String[] args) { try { run(ApiDatabaseGeneratorCommand.parse(args, CommandLineOrigin.INSTANCE).build());
diff --git a/src/main/java/com/android/tools/r8/ApiDatabaseGeneratorCommand.java b/src/main/java/com/android/tools/r8/ApiDatabaseGeneratorCommand.java index f3d557e..6695eb8 100644 --- a/src/main/java/com/android/tools/r8/ApiDatabaseGeneratorCommand.java +++ b/src/main/java/com/android/tools/r8/ApiDatabaseGeneratorCommand.java
@@ -21,15 +21,17 @@ private final List<Path> jarPaths; private final List<Path> xmlPaths; private final Path outputPath; + private final boolean amend; private final Reporter reporter; private final boolean printHelp; private final boolean printVersion; private ApiDatabaseGeneratorCommand( - List<Path> jarPaths, List<Path> xmlPaths, Path outputPath, Reporter reporter) { + List<Path> jarPaths, List<Path> xmlPaths, Path outputPath, boolean amend, Reporter reporter) { this.jarPaths = jarPaths; this.xmlPaths = xmlPaths; this.outputPath = outputPath; + this.amend = amend; this.reporter = reporter; this.printHelp = false; this.printVersion = false; @@ -39,6 +41,7 @@ this.jarPaths = Collections.emptyList(); this.xmlPaths = Collections.emptyList(); this.outputPath = null; + this.amend = false; this.reporter = new Reporter(); this.printHelp = printHelp; this.printVersion = printVersion; @@ -60,6 +63,10 @@ return xmlPaths; } + public boolean shouldAmend() { + return amend; + } + public Path getOutputPath() { return outputPath; } @@ -90,8 +97,10 @@ @KeepForApi public static class Builder { - private final List<Path> inputPaths = new ArrayList<>(); + private final List<Path> jarPaths = new ArrayList<>(); + private final List<Path> xmlPaths = new ArrayList<>(); private Path outputPath = null; + private boolean amend = true; private boolean printHelp = false; private boolean printVersion = false; private final Reporter reporter; @@ -104,8 +113,19 @@ this.reporter = new Reporter(diagnosticsHandler); } - public Builder addInputPath(Path inputPath) { - this.inputPaths.add(inputPath); + public Builder addJarPath(Path jarPath) { + this.jarPaths.add(jarPath); + return this; + } + + public Builder addXmlPath(Path xmlPath) { + this.xmlPaths.add(xmlPath); + return this; + } + + public Builder addSdkPath(Path sdkPath) { + addJarPath(sdkPath.resolve("android.jar")); + addXmlPath(sdkPath.resolve("data").resolve("api-versions.xml")); return this; } @@ -114,6 +134,11 @@ return this; } + public Builder setAmend(boolean amend) { + this.amend = amend; + return this; + } + public Builder setPrintHelp(boolean printHelp) { this.printHelp = printHelp; return this; @@ -132,22 +157,6 @@ ExceptionUtils.withDiagnosticsHandler( reporter, () -> { - List<Path> jarPaths = new ArrayList<>(); - List<Path> xmlPaths = new ArrayList<>(); - for (Path path : inputPaths) { - String name = path.getFileName().toString().toLowerCase(); - if (name.endsWith(".jar")) { - jarPaths.add(path); - } else if (name.endsWith(".xml")) { - xmlPaths.add(path); - } else { - error( - new StringDiagnostic( - "Unsupported input file extension: " - + path - + ". Must be either .jar or .xml")); - } - } if (jarPaths.isEmpty()) { error(new StringDiagnostic("At least one SDK JAR input path must be specified")); } @@ -157,7 +166,8 @@ if (outputPath == null) { outputPath = Paths.get(".", "api_database.ser"); } - box.set(new ApiDatabaseGeneratorCommand(jarPaths, xmlPaths, outputPath, reporter)); + box.set( + new ApiDatabaseGeneratorCommand(jarPaths, xmlPaths, outputPath, amend, reporter)); }, (message, cause, cancelled) -> new ApiDatabaseGeneratorException(message, cause)); return box.get();
diff --git a/src/main/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParser.java b/src/main/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParser.java index 415f4fa..09d00a0 100644 --- a/src/main/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParser.java +++ b/src/main/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParser.java
@@ -26,22 +26,42 @@ private static CliParser<ParserState> createParser() { String usageHeader = StringUtils.joinLines( - "Usage: apidatabasegenerator [options] <input-files>", - "where <input-files> are Android API XML files (e.g., api-versions.xml) to merge,", - "OR Android SDK Jar files (e.g., android.jar).", - "The XML lack sufficient information so the Jar files are used to add information to" - + " entries.", + "Usage: apidatabasegenerator [options]", + "Combines Android API information (--xml) with Android SDK information (--jar)", + "into an API database file required for compilation.", + "Multiple inputs of both are supported and any entries not present in both JAR and XML", + "form is trimmed away.", "The options are:"); CliParser<ParserState> parser = new CliParser<>(usageHeader); return parser .option0("--help", "Print help.", state -> state.builder.setPrintHelp(true), "-h") .option0("--version", "Print version.", state -> state.builder.setPrintVersion(true)) .option1( + "--jar", + "<jar-file>", + "Android SDK JAR file (e.g., android.jar).", + (state, arg) -> state.builder.addJarPath(Paths.get(arg))) + .option1( + "--xml", + "<xml-file>", + "Android API XML file (e.g., api-versions.xml).", + (state, arg) -> state.builder.addXmlPath(Paths.get(arg))) + .option1( + "--sdk", + "<sdk-dir>", + "Android SDK platform directory (e.g., $ANDROID_HOME/android-34).", + (state, arg) -> state.builder.addSdkPath(Paths.get(arg))) + .option1( "--output", "<database-file>", "Output result in <database-file> (must be a file, not a directory). Defaults to" + " 'api_database.ser'.", (state, arg) -> state.builder.setOutputPath(Paths.get(arg))) + .option0( + "--dont-amend", + "By default, the API database is amended with known missing information." + + " This option disables that and processes inputs directly as they are.", + state -> state.builder.setAmend(false)) .prefix2( "--map-diagnostics", "[:<type>]", @@ -59,8 +79,7 @@ toLevel, m -> state.builder.addDiagnosticsLevelMapping(m.from, m.diagnosticType, m.to), state.builder::error, - state.origin)) - .positional((state, arg) -> state.builder.addInputPath(Paths.get(arg))); + state.origin)); } public static ApiDatabaseGeneratorCommand.Builder parse(String[] args, Origin origin) {
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java index 90ea229..604e564 100644 --- a/src/main/java/com/android/tools/r8/D8Command.java +++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -563,9 +563,6 @@ if (getMode() != CompilationMode.RELEASE) { reporter.error("Option --reoptimize-dex requires --release"); } - if (proguardMapConsumer != null) { - reporter.error("Option --reoptimize-dex cannot be used with an output mapping file"); - } } super.validate(); }
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java index 839765e..4b9b8ed 100644 --- a/src/main/java/com/android/tools/r8/D8CommandParser.java +++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -162,15 +162,6 @@ } } - public static void main(String[] args) throws CompilationFailedException { - D8Command command = parse(args, Origin.root()).build(); - if (command.isPrintHelp()) { - System.out.println(getUsageMessage()); - } else { - D8.run(command); - } - } - static String getUsageMessage() { StringBuilder builder = new StringBuilder(); StringUtils.appendLines(
diff --git a/src/main/java/com/android/tools/r8/L8CommandParser.java b/src/main/java/com/android/tools/r8/L8CommandParser.java index e82d6c0..f0e679a 100644 --- a/src/main/java/com/android/tools/r8/L8CommandParser.java +++ b/src/main/java/com/android/tools/r8/L8CommandParser.java
@@ -35,15 +35,6 @@ // Note: this must be a subset of OPTIONS_WITH_ONE_PARAMETER. private static final Set<String> OPTIONS_WITH_TWO_PARAMETERS = ImmutableSet.of(ART_PROFILE_FLAG); - public static void main(String[] args) throws CompilationFailedException { - L8Command command = parse(args, Origin.root()).build(); - if (command.isPrintHelp()) { - System.out.println(getUsageMessage()); - } else { - L8.run(command); - } - } - static String getUsageMessage() { StringBuilder builder = new StringBuilder(); StringUtils.appendLines(
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiDataAccess.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiDataAccess.java index 91326a8..7510ece 100644 --- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiDataAccess.java +++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiDataAccess.java
@@ -4,6 +4,9 @@ package com.android.tools.r8.androidapi; +import static com.android.tools.r8.apimodel.AndroidApiHashingDatabaseBuilderGenerator.DATABASE_FORMAT_VERSION; +import static com.android.tools.r8.apimodel.AndroidApiHashingDatabaseBuilderGenerator.HEADER_SIZE; +import static com.android.tools.r8.apimodel.AndroidApiHashingDatabaseBuilderGenerator.MAGIC_BYTES; import static com.android.tools.r8.lightir.ByteUtils.unsetBitAtIndex; import com.android.tools.r8.DiagnosticsHandler; @@ -30,13 +33,46 @@ import java.nio.file.StandardOpenOption; import java.util.Map; import java.util.function.BiPredicate; +import java.util.function.Consumer; /** - * Implements low-level access methods for seeking on top of the database file defined by {@code + * Implements low-level access methods for seeking on top of the database file defined by {@link * AndroidApiLevelHashingDatabaseImpl} where a description of the format can also be found. */ public abstract class AndroidApiDataAccess { + private static Integer readHeader(CompatByteBuffer buffer) { + if (buffer == null || buffer.capacity() < HEADER_SIZE) { + return null; + } + for (int i = 0; i < MAGIC_BYTES.length; i++) { + if (buffer.get(i) != MAGIC_BYTES[i]) { + return null; + } + } + return buffer.getInt(MAGIC_BYTES.length); + } + + private static Integer readHeader(byte[] data) { + return data == null ? null : readHeader(CompatByteBuffer.wrap(data)); + } + + private static boolean isHeaderValid(Integer headerVersion, Consumer<String> errorHandler) { + if (headerVersion == null) { + errorHandler.accept("Invalid header"); + return false; + } else if (headerVersion != DATABASE_FORMAT_VERSION) { + errorHandler.accept( + "Incompatible database version (expected " + + DATABASE_FORMAT_VERSION + + " found " + + headerVersion + + ")"); + return false; + } + return true; + } + private static final String RESOURCE_NAME = "resources/api_database.ser"; private static final int ENTRY_SIZE_IN_BITS_FOR_CONSTANT_POOL_MAP = 17; private static final int ENTRY_SIZE_IN_BITS_FOR_API_MAP = 18; @@ -91,27 +127,9 @@ public static AndroidApiDataAccess create( InternalOptions options, DiagnosticsHandler diagnosticsHandler) { - URL resource; - Path apiDatabasePath = options.apiModelingOptions().apiDatabasePath; - if (apiDatabasePath != null) { - if (!Files.exists(apiDatabasePath)) { - diagnosticsHandler.error( - new StringDiagnostic("API database file does not exist: " + apiDatabasePath)); - return new AndroidApiDataAccessNoBacking(); - } - try { - resource = apiDatabasePath.toUri().toURL(); - } catch (IOException e) { - throw new Unreachable(e); - } - } else { - URL resourceUrl = AndroidApiDataAccess.class.getClassLoader().getResource(RESOURCE_NAME); - if (resourceUrl == null) { - diagnosticsHandler.warning( - new StringDiagnostic("Could not find the api database at " + RESOURCE_NAME)); - return new AndroidApiDataAccessNoBacking(); - } - resource = resourceUrl; + URL resource = getApiDatabaseUrl(options, diagnosticsHandler); + if (resource == null) { + return new AndroidApiDataAccessNoBacking(); } if (options.apiModelingOptions().useMemoryMappedByteBuffer) { @@ -120,7 +138,7 @@ // protocol: file, path: <path-to-file> // protocol: jar, path: file:<path-to-jar>!/<resource-name-in-jar> if (resource.getProtocol().equals("file")) { - return memoryMappedFromFile(Paths.get(resource.toURI())); + return memoryMappedFromFile(Paths.get(resource.toURI()), diagnosticsHandler); } else if (resource.getProtocol().equals("jar") && resource.getPath().startsWith("file:")) { // The path is on form 'file:<path-to-jar>!/<resource-name-in-jar> JarURLConnection jarUrl = (JarURLConnection) resource.openConnection(); @@ -133,7 +151,8 @@ return memoryMappedFromSectionOfFile( jarFile.toPath(), entry.getPayloadLocation().first, - entry.getPayloadLocation().last - entry.getPayloadLocation().first + 1); + entry.getPayloadLocation().last - entry.getPayloadLocation().first + 1, + diagnosticsHandler); } } } @@ -155,28 +174,68 @@ new StringDiagnostic("Could not open the api database at " + RESOURCE_NAME)); return new AndroidApiDataAccessNoBacking(); } - return new AndroidApiDataAccessInMemory(ByteStreams.toByteArray(apiInputStream)); + byte[] bytes = ByteStreams.toByteArray(apiInputStream); + boolean valid = + isHeaderValid( + readHeader(bytes), + err -> diagnosticsHandler.warning(new StringDiagnostic(err + " in " + resource))); + if (!valid) { + return new AndroidApiDataAccessNoBacking(); + } + return new AndroidApiDataAccessInMemory(bytes); } catch (IOException e) { diagnosticsHandler.warning(new ExceptionDiagnostic(e)); return new AndroidApiDataAccessNoBacking(); } } - private static AndroidApiDataAccessByteMapped memoryMappedFromFile(Path path) throws IOException { - FileChannel fileChannel = (FileChannel) Files.newByteChannel(path, StandardOpenOption.READ); - MappedByteBuffer mappedByteBuffer = - fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); - // Ensure that we can run on JDK 8 by using the CompatByteBuffer. - return new AndroidApiDataAccessByteMapped(new CompatByteBuffer(mappedByteBuffer)); + private static URL getApiDatabaseUrl( + InternalOptions options, DiagnosticsHandler diagnosticsHandler) { + Path apiDatabasePath = options.apiModelingOptions().apiDatabasePath; + if (apiDatabasePath != null) { + if (!Files.exists(apiDatabasePath)) { + diagnosticsHandler.error( + new StringDiagnostic("API database file does not exist: " + apiDatabasePath)); + return null; + } + try { + return apiDatabasePath.toUri().toURL(); + } catch (IOException e) { + throw new Unreachable(e); + } + } else { + URL resourceUrl = AndroidApiDataAccess.class.getClassLoader().getResource(RESOURCE_NAME); + if (resourceUrl == null) { + diagnosticsHandler.warning( + new StringDiagnostic("Could not find the api database at " + RESOURCE_NAME)); + return null; + } + return resourceUrl; + } } - private static AndroidApiDataAccessByteMapped memoryMappedFromSectionOfFile( - Path path, long offset, long length) throws IOException { + private static AndroidApiDataAccess memoryMappedFromFile( + Path path, DiagnosticsHandler diagnosticsHandler) throws IOException { + return memoryMappedFromSectionOfFile(path, 0, -1, diagnosticsHandler); + } + + private static AndroidApiDataAccess memoryMappedFromSectionOfFile( + Path path, long offset, long length, DiagnosticsHandler diagnosticsHandler) + throws IOException { FileChannel fileChannel = (FileChannel) Files.newByteChannel(path, StandardOpenOption.READ); MappedByteBuffer mappedByteBuffer = - fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, length); + fileChannel.map( + FileChannel.MapMode.READ_ONLY, offset, length < 0 ? fileChannel.size() : length); // Ensure that we can run on JDK 8 by using the CompatByteBuffer. - return new AndroidApiDataAccessByteMapped(new CompatByteBuffer(mappedByteBuffer)); + CompatByteBuffer bytes = new CompatByteBuffer(mappedByteBuffer); + boolean valid = + isHeaderValid( + readHeader(bytes), + err -> diagnosticsHandler.warning(new StringDiagnostic(err + " in " + path))); + if (!valid) { + return new AndroidApiDataAccessNoBacking(); + } + return new AndroidApiDataAccessByteMapped(bytes); } public static int entrySizeInBitsForConstantPoolMap() { @@ -211,9 +270,14 @@ return PAYLOAD_OFFSET_WITH_LENGTH; } + /** The start of the constant pool size */ + public static int constantPoolSizeOffset() { + return HEADER_SIZE; + } + /** The start of the constant pool */ public static int constantPoolOffset() { - return 4; + return HEADER_SIZE + 4; } /** The start of the constant pool hash map. */ @@ -357,7 +421,7 @@ @Override int readConstantPoolSize() { - return mappedByteBuffer.getInt(0); + return mappedByteBuffer.getInt(constantPoolSizeOffset()); } @Override @@ -426,7 +490,7 @@ @Override public int readConstantPoolSize() { - return readIntFromOffset(data, 0); + return readIntFromOffset(data, constantPoolSizeOffset()); } @Override
diff --git a/src/main/java/com/android/tools/r8/apimodel/AmendApiFromResources.java b/src/main/java/com/android/tools/r8/apimodel/AmendApiFromResources.java new file mode 100644 index 0000000..0aac870 --- /dev/null +++ b/src/main/java/com/android/tools/r8/apimodel/AmendApiFromResources.java
@@ -0,0 +1,40 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.apimodel; + +import com.android.tools.r8.ApiDatabaseGeneratorException; +import com.android.tools.r8.apimodel.jar.ApiJarInfo; +import com.android.tools.r8.utils.internal.ThrowingFunction; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; + +public class AmendApiFromResources { + + public static void applyAmendments(Collection<ParsedApiClass> classes, ApiJarInfo jarInfo) + throws ApiDatabaseGeneratorException, IOException { + ApiAmendments missingApi = + readResource("/resources/missing.api.txt", ApiAmendmentsParser::parseApiAmendments); + ApiAmendments hiddenApi = + readResource("/resources/hidden.api.txt", ApiAmendmentsParser::parseApiAmendments); + JarAmendments jarHidden = + readResource("/resources/hidden.jar.txt", ApiAmendmentsParser::parseJarAmendments); + + ParsedApiClassAmending.amendApi(classes, missingApi); + ParsedApiClassAmending.amendApi(classes, hiddenApi); + ApiJarInfoAmending.amendJar(jarInfo, jarHidden); + } + + private static <T, E extends Throwable> T readResource( + String resourceName, ThrowingFunction<InputStream, T, E> consumer) + throws IOException, ApiDatabaseGeneratorException, E { + try (InputStream stream = AmendApiFromResources.class.getResourceAsStream(resourceName)) { + if (stream == null) { + throw new ApiDatabaseGeneratorException("Resource " + resourceName + " not found"); + } + return consumer.apply(stream); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java b/src/main/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java index 2e5ef8b..800cccb 100644 --- a/src/main/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java +++ b/src/main/java/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGenerator.java
@@ -26,6 +26,7 @@ import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -40,6 +41,17 @@ public class AndroidApiHashingDatabaseBuilderGenerator { + public static final String MAGIC = "r8-apidatabase"; + public static final byte[] MAGIC_BYTES = MAGIC.getBytes(StandardCharsets.US_ASCII); + public static final int HEADER_SIZE = MAGIC_BYTES.length + 4; + + /** + * This version must be increased whenever the output of {@link + * com.android.tools.r8.ApiDatabaseGenerator} changes. This can be because of changes to + * amendments or changes to the underlying format. + */ + public static final int DATABASE_FORMAT_VERSION = 1; + public static class GenerationException extends Exception { public GenerationException(String message) { super(message); @@ -108,6 +120,8 @@ * number of unsigned bytes): * * <pre> + * magic: r8-apidatabase + * format_version: u4 * constant_pool_size: u4 * constant_pool: [constant_pool_size * payload_entry] * constant_pool_map: [0..max_hash(ConstantPoolEntry) * payload_entry] @@ -187,6 +201,10 @@ offsetMap.put(entry.getIntKey(), Pair.create(startingOffset, length)); } + // Write header: magic string and database format version. + outputStream.write(MAGIC_BYTES); + outputStream.writeInt(DATABASE_FORMAT_VERSION); + // Write constant pool size <u4:size>. outputStream.writeInt(constantPool.size());
diff --git a/src/main/java/com/android/tools/r8/apimodel/ApiAmendments.java b/src/main/java/com/android/tools/r8/apimodel/ApiAmendments.java new file mode 100644 index 0000000..15c79e9 --- /dev/null +++ b/src/main/java/com/android/tools/r8/apimodel/ApiAmendments.java
@@ -0,0 +1,96 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.apimodel; + +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.utils.AndroidApiLevel; +import java.util.ArrayList; +import java.util.List; + +public class ApiAmendments { + + public static class ClassAmendment { + private final ClassReference classReference; + private final AndroidApiLevel apiLevel; + + public ClassAmendment(ClassReference classReference, AndroidApiLevel apiLevel) { + this.classReference = classReference; + this.apiLevel = apiLevel; + } + + public ClassReference getClassReference() { + return classReference; + } + + public AndroidApiLevel getApiLevel() { + return apiLevel; + } + } + + public static class MethodAmendment { + private final MethodReference methodReference; + private final AndroidApiLevel apiLevel; + + public MethodAmendment(MethodReference methodReference, AndroidApiLevel apiLevel) { + this.methodReference = methodReference; + this.apiLevel = apiLevel; + } + + public MethodReference getMethodReference() { + return methodReference; + } + + public AndroidApiLevel getApiLevel() { + return apiLevel; + } + } + + public static class FieldAmendment { + private final FieldTypelessReference fieldReference; + private final AndroidApiLevel apiLevel; + + public FieldAmendment(FieldTypelessReference fieldReference, AndroidApiLevel apiLevel) { + this.fieldReference = fieldReference; + this.apiLevel = apiLevel; + } + + public FieldTypelessReference getFieldReference() { + return fieldReference; + } + + public AndroidApiLevel getApiLevel() { + return apiLevel; + } + } + + private final List<ClassAmendment> classes = new ArrayList<>(); + private final List<MethodAmendment> methods = new ArrayList<>(); + private final List<FieldAmendment> fields = new ArrayList<>(); + + public void addClass(ClassReference classReference, AndroidApiLevel apiLevel) { + classes.add(new ClassAmendment(classReference, apiLevel)); + } + + public void addMethod(MethodReference methodReference, AndroidApiLevel apiLevel) { + methods.add(new MethodAmendment(methodReference, apiLevel)); + } + + public void addField(FieldTypelessReference fieldReference, AndroidApiLevel apiLevel) { + fields.add(new FieldAmendment(fieldReference, apiLevel)); + } + + public List<ClassAmendment> getClasses() { + return classes; + } + + public List<MethodAmendment> getMethods() { + return methods; + } + + public List<FieldAmendment> getFields() { + return fields; + } +}
diff --git a/src/main/java/com/android/tools/r8/apimodel/ApiAmendmentsParser.java b/src/main/java/com/android/tools/r8/apimodel/ApiAmendmentsParser.java new file mode 100644 index 0000000..5434832 --- /dev/null +++ b/src/main/java/com/android/tools/r8/apimodel/ApiAmendmentsParser.java
@@ -0,0 +1,234 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.apimodel; + +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.google.common.collect.ImmutableList; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Parser for API and Jar amendments files (*.api.txt and *.jar.txt). This format is only written + * and read internally so it is free to change. + * + * <p>Empty lines and lines starting with {@code //} or {@code #} are ignored. + * + * <p><b>API Amendments format (*.api.txt):</b> + * + * <ul> + * <li>{@code class <class-descriptor> <api-level>} + * <li>{@code method <holder-descriptor> <method-name> <method-descriptor> <api-level>} + * <li>{@code field <holder-descriptor> <field-name> <api-level>} + * </ul> + * + * <p><b>Jar Amendments format (*.jar.txt):</b> + * + * <ul> + * <li>{@code class <class-descriptor> extends <super-descriptor> implements <interfaces|none>} + * <li>{@code interface <interface-descriptor> extends <interfaces|none>} + * <li>{@code method <holder-descriptor> <method-name> <method-descriptor> <static|instance>} + * <li>{@code field <holder-descriptor> <field-name>} + * </ul> + * + * <p>Multiple interfaces are comma-separated without spaces (e.g. {@code + * Ljava/lang/Cloneable;,Ljava/io/Serializable;}) or {@code none} if there are no interfaces. + */ +public class ApiAmendmentsParser { + + public static ApiAmendments parseApiAmendments(InputStream inputStream) throws IOException { + ApiAmendments amendments = new ApiAmendments(); + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + String line; + int lineNumber = 0; + while ((line = reader.readLine()) != null) { + lineNumber++; + String[] parts = splitLine(line); + if (parts == null) { + continue; + } + parseApiLine(parts[0], parts, amendments, lineNumber); + } + } + return amendments; + } + + private static void parseApiLine( + String type, String[] parts, ApiAmendments amendments, int lineNumber) { + switch (type) { + case "class": + parseClassAmendment(parts, amendments, lineNumber); + break; + case "method": + parseMethodAmendment(parts, amendments, lineNumber); + break; + case "field": + parseFieldAmendment(parts, amendments, lineNumber); + break; + default: + throw new RuntimeException("Unknown type " + type + " at line " + lineNumber); + } + } + + private static void parseClassAmendment( + String[] parts, ApiAmendments amendments, int lineNumber) { + if (parts.length != 3) { + throw new RuntimeException("Invalid class amendment at line " + lineNumber); + } + ClassReference classReference = Reference.classFromDescriptor(parts[1]); + AndroidApiLevel apiLevel = AndroidApiLevel.parseAndroidApiLevel(parts[2]); + amendments.addClass(classReference, apiLevel); + } + + private static void parseMethodAmendment( + String[] parts, ApiAmendments amendments, int lineNumber) { + if (parts.length != 5) { + throw new RuntimeException("Invalid method amendment at line " + lineNumber); + } + ClassReference holder = Reference.classFromDescriptor(parts[1]); + String name = parts[2]; + String descriptor = parts[3]; + AndroidApiLevel apiLevel = AndroidApiLevel.parseAndroidApiLevel(parts[4]); + amendments.addMethod(Reference.methodFromDescriptor(holder, name, descriptor), apiLevel); + } + + private static void parseFieldAmendment( + String[] parts, ApiAmendments amendments, int lineNumber) { + if (parts.length != 4) { + throw new RuntimeException("Invalid field amendment at line " + lineNumber); + } + ClassReference holder = Reference.classFromDescriptor(parts[1]); + String name = parts[2]; + AndroidApiLevel apiLevel = AndroidApiLevel.parseAndroidApiLevel(parts[3]); + amendments.addField(new FieldTypelessReference(holder, name), apiLevel); + } + + public static JarAmendments parseJarAmendments(InputStream inputStream) throws IOException { + JarAmendments amendments = new JarAmendments(); + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + String line; + int lineNumber = 0; + while ((line = reader.readLine()) != null) { + lineNumber++; + String[] parts = splitLine(line); + if (parts == null) { + continue; + } + parseJarLine(parts[0], parts, amendments, lineNumber); + } + } + return amendments; + } + + private static void parseJarLine( + String type, String[] parts, JarAmendments amendments, int lineNumber) { + switch (type) { + case "class": + parseJarClassAmendment(parts, amendments, lineNumber); + break; + case "interface": + parseJarInterfaceAmendment(parts, amendments, lineNumber); + break; + case "method": + parseJarMethodAmendment(parts, amendments, lineNumber); + break; + case "field": + parseJarFieldAmendment(parts, amendments, lineNumber); + break; + default: + throw new RuntimeException("Unknown type " + type + " at line " + lineNumber); + } + } + + private static String[] splitLine(String line) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("//") || line.startsWith("#")) { + return null; + } else { + String[] split = line.split(" "); + assert split.length >= 1; + return split; + } + } + + private static void parseJarClassAmendment( + String[] parts, JarAmendments amendments, int lineNumber) { + if (parts.length != 6) { + throw new RuntimeException("Invalid jar class amendment at line " + lineNumber); + } + ClassReference classReference = Reference.classFromDescriptor(parts[1]); + if (!parts[2].equals("extends")) { + throw new RuntimeException("Expected 'extends' as third word at line " + lineNumber); + } + ClassReference superClass = Reference.classFromDescriptor(parts[3]); + if (!parts[4].equals("implements")) { + throw new RuntimeException("Expected 'implements' as fifth word at line " + lineNumber); + } + List<ClassReference> interfaces = parseInterfaces(parts[5]); + amendments.addClass(classReference, superClass, interfaces); + } + + private static void parseJarInterfaceAmendment( + String[] parts, JarAmendments amendments, int lineNumber) { + if (parts.length != 4) { + throw new RuntimeException("Invalid jar interface amendment at line " + lineNumber); + } + ClassReference interfaceReference = Reference.classFromDescriptor(parts[1]); + if (!parts[2].equals("extends")) { + throw new RuntimeException("Expected 'extends' as third word at line " + lineNumber); + } + List<ClassReference> interfaces = parseInterfaces(parts[3]); + amendments.addInterface(interfaceReference, interfaces); + } + + private static List<ClassReference> parseInterfaces(String interfacesStr) { + if (interfacesStr.equals("none")) { + return ImmutableList.of(); + } + return Arrays.stream(interfacesStr.split(",")) + .map(Reference::classFromDescriptor) + .collect(Collectors.toList()); + } + + private static void parseJarMethodAmendment( + String[] parts, JarAmendments amendments, int lineNumber) { + if (parts.length != 5) { + throw new RuntimeException("Invalid jar method amendment at line " + lineNumber); + } + ClassReference holder = Reference.classFromDescriptor(parts[1]); + String name = parts[2]; + String descriptor = parts[3]; + String relation = parts[4]; + boolean isStatic; + if (relation.equals("static")) { + isStatic = true; + } else if (relation.equals("instance")) { + isStatic = false; + } else { + throw new RuntimeException( + "Expected 'static' or 'instance' as fifth word at line " + lineNumber); + } + amendments.addMethod(holder, name, descriptor, isStatic); + } + + private static void parseJarFieldAmendment( + String[] parts, JarAmendments amendments, int lineNumber) { + if (parts.length != 3) { + throw new RuntimeException("Invalid jar field amendment at line " + lineNumber); + } + ClassReference holder = Reference.classFromDescriptor(parts[1]); + String name = parts[2]; + amendments.addField(holder, name); + } +}
diff --git a/src/main/java/com/android/tools/r8/apimodel/ApiJarInfoAmending.java b/src/main/java/com/android/tools/r8/apimodel/ApiJarInfoAmending.java new file mode 100644 index 0000000..38e8a25 --- /dev/null +++ b/src/main/java/com/android/tools/r8/apimodel/ApiJarInfoAmending.java
@@ -0,0 +1,110 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.apimodel; + +import com.android.tools.r8.ApiDatabaseGeneratorException; +import com.android.tools.r8.apimodel.JarAmendments.ClassAmendment; +import com.android.tools.r8.apimodel.JarAmendments.FieldAmendment; +import com.android.tools.r8.apimodel.JarAmendments.InterfaceAmendment; +import com.android.tools.r8.apimodel.JarAmendments.MethodAmendment; +import com.android.tools.r8.apimodel.jar.ApiClassInfo; +import com.android.tools.r8.apimodel.jar.ApiJarInfo; +import com.android.tools.r8.references.ClassReference; + +public class ApiJarInfoAmending { + + public static void amendJar(ApiJarInfo jarInfo, JarAmendments jarAmendments) + throws ApiDatabaseGeneratorException { + for (ClassAmendment classAmendment : jarAmendments.getClasses()) { + amendJarClass(jarInfo, classAmendment); + } + for (JarAmendments.InterfaceAmendment interfaceAmendment : jarAmendments.getInterfaces()) { + amendJarInterface(jarInfo, interfaceAmendment); + } + for (MethodAmendment methodAmendment : jarAmendments.getMethods()) { + amendJarMethod(jarInfo, methodAmendment); + } + for (FieldAmendment fieldAmendment : jarAmendments.getFields()) { + amendJarField(jarInfo, fieldAmendment); + } + } + + private static void amendJarClass(ApiJarInfo jarInfo, ClassAmendment classAmendment) + throws ApiDatabaseGeneratorException { + ClassReference classRef = classAmendment.getClassReference(); + if (jarInfo.hasClass(classRef)) { + throw new ApiDatabaseGeneratorException( + "Hidden jar class " + classRef + " already exists in JAR info"); + } + + String superClassBin = + classAmendment.getSuperClass() != null + ? classAmendment.getSuperClass().getBinaryName() + : null; + + ApiClassInfo classInfo = new ApiClassInfo(classRef.getBinaryName(), superClassBin, false); + classInfo.addInterfaces(classAmendment.getInterfaces(), ClassReference::getBinaryName); + jarInfo.addClass(classInfo); + } + + private static void amendJarInterface(ApiJarInfo jarInfo, InterfaceAmendment interfaceAmendment) + throws ApiDatabaseGeneratorException { + ClassReference ifaceRef = interfaceAmendment.getInterfaceReference(); + if (jarInfo.hasClass(ifaceRef)) { + throw new ApiDatabaseGeneratorException( + "Hidden jar interface " + ifaceRef + " already exists in JAR info"); + } + + ApiClassInfo classInfo = new ApiClassInfo(ifaceRef.getBinaryName(), null, true); + classInfo.addInterfaces(interfaceAmendment.getInterfaces(), ClassReference::getBinaryName); + jarInfo.addClass(classInfo); + } + + private static void amendJarMethod(ApiJarInfo jarInfo, MethodAmendment methodAmendment) + throws ApiDatabaseGeneratorException { + ClassReference holderRef = methodAmendment.getHolder(); + ApiClassInfo holder = jarInfo.getClassInfo(holderRef); + if (holder == null) { + throw new ApiDatabaseGeneratorException( + "Holder class " + + holderRef + + " for hidden jar method " + + methodAmendment.getName() + + " does not exist"); + } + if (holder.hasMethod(methodAmendment.getName(), methodAmendment.getDescriptor())) { + throw new ApiDatabaseGeneratorException( + "Hidden jar method " + + methodAmendment.getName() + + methodAmendment.getDescriptor() + + " already exists in holder " + + holderRef); + } + holder.addMethod( + methodAmendment.getName(), methodAmendment.getDescriptor(), methodAmendment.isStatic()); + } + + private static void amendJarField(ApiJarInfo jarInfo, FieldAmendment fieldAmendment) + throws ApiDatabaseGeneratorException { + ClassReference holderRef = fieldAmendment.getHolder(); + ApiClassInfo holder = jarInfo.getClassInfo(holderRef); + if (holder == null) { + throw new ApiDatabaseGeneratorException( + "Holder class " + + holderRef + + " for hidden jar field " + + fieldAmendment.getName() + + " does not exist"); + } + if (holder.hasField(fieldAmendment.getName())) { + throw new ApiDatabaseGeneratorException( + "Hidden jar field " + + fieldAmendment.getName() + + " already exists in holder " + + holderRef); + } + holder.addField(fieldAmendment.getName()); + } +}
diff --git a/src/main/java/com/android/tools/r8/apimodel/JarAmendments.java b/src/main/java/com/android/tools/r8/apimodel/JarAmendments.java new file mode 100644 index 0000000..5ff6208 --- /dev/null +++ b/src/main/java/com/android/tools/r8/apimodel/JarAmendments.java
@@ -0,0 +1,143 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.apimodel; + +import com.android.tools.r8.references.ClassReference; +import java.util.ArrayList; +import java.util.List; + +public class JarAmendments { + + public static class ClassAmendment { + private final ClassReference classReference; + private final ClassReference superClass; + private final List<ClassReference> interfaces; + + public ClassAmendment( + ClassReference classReference, ClassReference superClass, List<ClassReference> interfaces) { + this.classReference = classReference; + this.superClass = superClass; + this.interfaces = interfaces; + } + + public ClassReference getClassReference() { + return classReference; + } + + public ClassReference getSuperClass() { + return superClass; + } + + public List<ClassReference> getInterfaces() { + return interfaces; + } + } + + public static class InterfaceAmendment { + private final ClassReference interfaceReference; + private final List<ClassReference> interfaces; + + public InterfaceAmendment(ClassReference interfaceReference, List<ClassReference> interfaces) { + this.interfaceReference = interfaceReference; + this.interfaces = interfaces; + } + + public ClassReference getInterfaceReference() { + return interfaceReference; + } + + public List<ClassReference> getInterfaces() { + return interfaces; + } + } + + public static class MethodAmendment { + private final ClassReference holder; + private final String name; + private final String descriptor; + private final boolean isStatic; + + public MethodAmendment( + ClassReference holder, String name, String descriptor, boolean isStatic) { + this.holder = holder; + this.name = name; + this.descriptor = descriptor; + this.isStatic = isStatic; + } + + public ClassReference getHolder() { + return holder; + } + + public String getName() { + return name; + } + + public String getDescriptor() { + return descriptor; + } + + public boolean isStatic() { + return isStatic; + } + } + + public static class FieldAmendment { + private final ClassReference holder; + private final String name; + + public FieldAmendment(ClassReference holder, String name) { + this.holder = holder; + this.name = name; + } + + public ClassReference getHolder() { + return holder; + } + + public String getName() { + return name; + } + } + + private final List<ClassAmendment> classes = new ArrayList<>(); + private final List<InterfaceAmendment> interfaces = new ArrayList<>(); + private final List<MethodAmendment> methods = new ArrayList<>(); + private final List<FieldAmendment> fields = new ArrayList<>(); + + public void addClass( + ClassReference classReference, ClassReference superClass, List<ClassReference> interfaces) { + classes.add(new ClassAmendment(classReference, superClass, interfaces)); + } + + public void addInterface( + ClassReference interfaceReference, List<ClassReference> implementedInterfaces) { + interfaces.add(new InterfaceAmendment(interfaceReference, implementedInterfaces)); + } + + public void addMethod(ClassReference holder, String name, String descriptor, boolean isStatic) { + methods.add(new MethodAmendment(holder, name, descriptor, isStatic)); + } + + public void addField(ClassReference holder, String name) { + fields.add(new FieldAmendment(holder, name)); + } + + public List<ClassAmendment> getClasses() { + return classes; + } + + public List<InterfaceAmendment> getInterfaces() { + return interfaces; + } + + public List<MethodAmendment> getMethods() { + return methods; + } + + public List<FieldAmendment> getFields() { + return fields; + } +}
diff --git a/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassAmending.java b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassAmending.java new file mode 100644 index 0000000..b9e893d --- /dev/null +++ b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassAmending.java
@@ -0,0 +1,92 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.apimodel; + +import com.android.tools.r8.ApiDatabaseGeneratorException; +import com.android.tools.r8.apimodel.ApiAmendments.ClassAmendment; +import com.android.tools.r8.apimodel.ApiAmendments.FieldAmendment; +import com.android.tools.r8.apimodel.ApiAmendments.MethodAmendment; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.MethodReference; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class ParsedApiClassAmending { + + public static void amendApi(Collection<ParsedApiClass> apiClasses, ApiAmendments amendments) + throws ApiDatabaseGeneratorException { + Map<ClassReference, ParsedApiClass> classMap = new HashMap<>(); + for (ParsedApiClass apiClass : apiClasses) { + classMap.put(apiClass.getClassReference(), apiClass); + } + + for (ApiAmendments.ClassAmendment classAmendment : amendments.getClasses()) { + amendApiClass(apiClasses, classAmendment, classMap); + } + for (ApiAmendments.MethodAmendment methodAmendment : amendments.getMethods()) { + amendApiMethod(methodAmendment, classMap); + } + for (ApiAmendments.FieldAmendment fieldAmendment : amendments.getFields()) { + amendApiField(fieldAmendment, classMap); + } + } + + private static void amendApiField( + FieldAmendment fieldAmendment, Map<ClassReference, ParsedApiClass> classMap) + throws ApiDatabaseGeneratorException { + FieldTypelessReference fieldRef = fieldAmendment.getFieldReference(); + ParsedApiClass holder = classMap.get(fieldRef.getHolderClass()); + if (holder == null) { + throw new ApiDatabaseGeneratorException( + "Holder class " + + fieldRef.getHolderClass() + + " for field " + + fieldRef + + " does not exist"); + } + if (holder.hasField(fieldRef)) { + throw new ApiDatabaseGeneratorException( + "Field " + fieldRef + " already exists in " + holder.getClassReference()); + } + holder.registerField(fieldRef, new ApiRange(fieldAmendment.getApiLevel())); + } + + private static void amendApiMethod( + MethodAmendment methodAmendment, Map<ClassReference, ParsedApiClass> classMap) + throws ApiDatabaseGeneratorException { + MethodReference methodRef = methodAmendment.getMethodReference(); + ParsedApiClass holder = classMap.get(methodRef.getHolderClass()); + if (holder == null) { + throw new ApiDatabaseGeneratorException( + "Holder class " + + methodRef.getHolderClass() + + " for method " + + methodRef + + " does not exist"); + } + if (holder.hasMethod(methodRef)) { + throw new ApiDatabaseGeneratorException( + "Method " + methodRef + " already exists in " + holder.getClassReference()); + } + holder.registerMethod(methodRef, new ApiRange(methodAmendment.getApiLevel())); + } + + private static void amendApiClass( + Collection<ParsedApiClass> apiClasses, + ClassAmendment classAmendment, + Map<ClassReference, ParsedApiClass> classMap) + throws ApiDatabaseGeneratorException { + ClassReference classRef = classAmendment.getClassReference(); + if (classMap.containsKey(classRef)) { + throw new ApiDatabaseGeneratorException("Class " + classRef + " already exists"); + } else { + ParsedApiClass newClass = + new ParsedApiClass(classRef, new ApiRange(classAmendment.getApiLevel())); + apiClasses.add(newClass); + classMap.put(classRef, newClass); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassFlattening.java b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassFlattening.java index f509379..07543a0 100644 --- a/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassFlattening.java +++ b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassFlattening.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.references.MethodReference; import com.android.tools.r8.references.Reference; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashMap; @@ -45,7 +46,7 @@ for (ClassReference ref : classMap.keySet()) { flattenClass(ref); } - return flattenedCache.values(); + return new ArrayList<>(flattenedCache.values()); } /** Returns a flattened class of {@code ref} with all inherited members except from Object. */
diff --git a/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassMerging.java b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassMerging.java index f1e2779..f7e961d 100644 --- a/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassMerging.java +++ b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassMerging.java
@@ -8,6 +8,7 @@ import com.android.tools.r8.DiagnosticsHandler; import com.android.tools.r8.androidapi.DuplicateApiDatabaseEntryDiagnostic; import com.android.tools.r8.references.ClassReference; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -27,7 +28,7 @@ throws ApiDatabaseGeneratorException { ParsedApiClassMerging merger = new ParsedApiClassMerging(diagnosticsHandler); merger.merge(parsedClasses); - return merger.merged.values(); + return new ArrayList<>(merger.merged.values()); } private void merge(Iterable<ParsedApiClass> classes) throws ApiDatabaseGeneratorException {
diff --git a/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassSorting.java b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassSorting.java new file mode 100644 index 0000000..f4fa0a4 --- /dev/null +++ b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassSorting.java
@@ -0,0 +1,95 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.apimodel; + +import com.android.tools.r8.ApiDatabaseGeneratorException; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.MethodReference; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; + +public class ParsedApiClassSorting { + + private static final Comparator<ClassReference> CLASS_REFERENCE_COMPARATOR = + Comparator.comparing(ClassReference::getDescriptor); + + private static final Comparator<MethodReference> METHOD_REFERENCE_COMPARATOR = + Comparator.comparing(MethodReference::getMethodName) + .thenComparing(MethodReference::getMethodDescriptor) + .thenComparing(method -> method.getHolderClass().getDescriptor()); + + private static final Comparator<FieldTypelessReference> FIELD_REFERENCE_COMPARATOR = + Comparator.comparing(FieldTypelessReference::getFieldName) + .thenComparing(field -> field.getHolderClass().getDescriptor()); + + private static final Comparator<ParsedApiClass> PARSED_API_CLASS_COMPARATOR = + Comparator.comparing(ParsedApiClass::getClassReference, CLASS_REFERENCE_COMPARATOR); + + public static Collection<ParsedApiClass> sorted(Collection<ParsedApiClass> apiClasses) + throws ApiDatabaseGeneratorException { + List<ParsedApiClass> sortedClasses = new ArrayList<>(apiClasses.size()); + for (ParsedApiClass apiClass : apiClasses) { + sortedClasses.add(sortedClass(apiClass)); + } + return uniquelySorted( + sortedClasses, PARSED_API_CLASS_COMPARATOR, ParsedApiClass::getClassReference); + } + + private static ParsedApiClass sortedClass(ParsedApiClass apiClass) + throws ApiDatabaseGeneratorException { + ParsedApiClass sortedClass = + new ParsedApiClass(apiClass.getClassReference(), apiClass.getRange()); + + List<ClassReference> supertypes = new ArrayList<>(); + apiClass.forEachSupertype((ref, range) -> supertypes.add(ref)); + for (ClassReference supertype : uniquelySorted(supertypes, CLASS_REFERENCE_COMPARATOR)) { + sortedClass.registerSupertype(supertype, apiClass.getSupertypeRange(supertype)); + } + + List<ClassReference> interfaces = new ArrayList<>(); + apiClass.forEachInterface((ref, range) -> interfaces.add(ref)); + for (ClassReference iface : uniquelySorted(interfaces, CLASS_REFERENCE_COMPARATOR)) { + sortedClass.registerInterface(iface, apiClass.getInterfaceRange(iface)); + } + + List<MethodReference> methods = new ArrayList<>(); + apiClass.forEachMethod((ref, range) -> methods.add(ref)); + for (MethodReference method : uniquelySorted(methods, METHOD_REFERENCE_COMPARATOR)) { + sortedClass.registerMethod(method, apiClass.getMethodRange(method)); + } + + List<FieldTypelessReference> fields = new ArrayList<>(); + apiClass.forEachField((ref, range) -> fields.add(ref)); + for (FieldTypelessReference field : uniquelySorted(fields, FIELD_REFERENCE_COMPARATOR)) { + sortedClass.registerField(field, apiClass.getFieldRange(field)); + } + + return sortedClass; + } + + private static <T> List<T> uniquelySorted( + Collection<T> items, Comparator<T> comparator, Function<T, ?> toMessage) + throws ApiDatabaseGeneratorException { + List<T> sorted = new ArrayList<>(items); + sorted.sort(comparator); + T previous = null; + for (T current : sorted) { + if (previous != null && comparator.compare(previous, current) == 0) { + throw new ApiDatabaseGeneratorException( + "Found duplicates for: " + toMessage.apply(current)); + } + previous = current; + } + return sorted; + } + + private static <T> List<T> uniquelySorted(Collection<T> items, Comparator<T> comparator) + throws ApiDatabaseGeneratorException { + return uniquelySorted(items, comparator, Object::toString); + } +}
diff --git a/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassTrimming.java b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassTrimming.java index 7245ff4..ea71d6c 100644 --- a/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassTrimming.java +++ b/src/main/java/com/android/tools/r8/apimodel/ParsedApiClassTrimming.java
@@ -35,6 +35,8 @@ SkipAnswer skipField(ClassReference clazz, FieldTypelessReference field, ApiRange range) throws E; + + void done() throws E; } public static <E extends Throwable> Collection<ParsedApiClass> trim( @@ -45,6 +47,7 @@ result.add(trim(clazz, trimmer)); } } + trimmer.done(); return result; } @@ -107,6 +110,11 @@ ClassReference clazz, FieldTypelessReference field, ApiRange range) { return range.isRemoved() ? SkipAnswer.SKIP : SkipAnswer.KEEP; } + + @Override + public void done() throws RuntimeException { + // Do nothing. + } } public static class JarTrimmer @@ -117,6 +125,10 @@ this.jarInfo = jarInfo; } + public ApiJarInfo getJarInfo() { + return jarInfo; + } + @Override public SkipAnswer skipClass(ClassReference clazz, ApiRange range) { return SkipAnswer.skipIf(!jarInfo.hasClass(clazz)); @@ -160,6 +172,11 @@ throws ApiDatabaseGeneratorException { return SkipAnswer.skipIf(!jarInfo.hasField(clazz.getBinaryName(), field.getFieldName())); } + + @Override + public void done() throws ApiDatabaseGeneratorException { + // Do nothing. + } } public interface TrimmerListener { @@ -176,6 +193,8 @@ void skipField( ClassReference clazz, FieldTypelessReference field, ApiRange range, SkipAnswer answer); + + void done(); } public static class ListeningDecorator<E extends Throwable> implements Trimmer<E> { @@ -226,5 +245,11 @@ listener.skipField(clazz, field, range, result); return result; } + + @Override + public void done() throws E { + trimmer.done(); + listener.done(); + } } }
diff --git a/src/main/java/com/android/tools/r8/apimodel/jar/ApiClassInfo.java b/src/main/java/com/android/tools/r8/apimodel/jar/ApiClassInfo.java index 5bd8a9c..148d776 100644 --- a/src/main/java/com/android/tools/r8/apimodel/jar/ApiClassInfo.java +++ b/src/main/java/com/android/tools/r8/apimodel/jar/ApiClassInfo.java
@@ -5,58 +5,76 @@ package com.android.tools.r8.apimodel.jar; import com.android.tools.r8.utils.DescriptorUtils; -import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.function.Function; public class ApiClassInfo { - private final String binaryName; + private final String internalName; private final String superClass; - private final Set<String> interfaces; private final boolean isInterface; + private final Set<String> interfaces; private final Set<ApiMethodInfo> methods; private final Set<String> fields; - public ApiClassInfo( - String binaryName, + public ApiClassInfo(String internalName, String superClass, boolean isInterface) { + this(internalName, superClass, isInterface, new HashSet<>(), new HashSet<>(), new HashSet<>()); + } + + private ApiClassInfo( + String internalName, String superClass, - Collection<String> interfaces, boolean isInterface, - Collection<ApiMethodInfo> methods, - Collection<String> fields) { - assert binaryName != null; - assert DescriptorUtils.isValidInternalName(binaryName); + Set<String> interfaces, + Set<ApiMethodInfo> methods, + Set<String> fields) { + assert internalName != null; + assert DescriptorUtils.isValidInternalName(internalName); assert superClass == null || DescriptorUtils.isValidInternalName(superClass); assert interfaces != null; assert interfaces.stream().allMatch(DescriptorUtils::isValidInternalName); assert methods != null; assert fields != null; - this.binaryName = binaryName; + this.internalName = internalName; this.superClass = superClass; - this.interfaces = new HashSet<>(interfaces); + this.interfaces = interfaces; this.isInterface = isInterface; - this.methods = new HashSet<>(methods); - this.fields = new HashSet<>(fields); + this.methods = methods; + this.fields = fields; } public String getBinaryName() { - return binaryName; + return internalName; } public String getSuperClass() { return superClass; } - public boolean implementsInterface(String binaryInterfaceName) { - return interfaces.contains(binaryInterfaceName); + public boolean implementsInterface(String internalInterfaceName) { + return interfaces.contains(internalInterfaceName); } public boolean isInterface() { return isInterface; } - public Iterable<String> getInterfaces() { - return interfaces; + /** Returns a read-only set. */ + public Set<String> getInterfaces() { + return Collections.unmodifiableSet(interfaces); + } + + public void addInterface(String internalInterfaceName) { + assert DescriptorUtils.isValidInternalName(internalInterfaceName) + : internalInterfaceName + " is not a valid name"; + assert !interfaces.contains(internalInterfaceName) + : internalInterfaceName + " is already present"; + interfaces.add(internalInterfaceName); + } + + public <T> void addInterfaces(Iterable<T> interfaces, Function<T, String> getInternalName) { + interfaces.forEach(i -> addInterface(getInternalName.apply(i))); } public boolean hasMethod(String name, String descriptor) { @@ -68,7 +86,7 @@ ApiMethodInfo instanceMethod = new ApiMethodInfo(name, descriptor, false); if (methods.contains(staticMethod)) { assert !methods.contains(instanceMethod) - : this.binaryName + : this.internalName + "." + name + descriptor @@ -82,25 +100,45 @@ } public void addMethod(String name, String descriptor, boolean isStatic) { - assert !hasMethod(name, descriptor) - : "method already exists " + this.binaryName + "." + name + descriptor; - methods.add(new ApiMethodInfo(name, descriptor, isStatic)); + addMethod(new ApiMethodInfo(name, descriptor, isStatic)); } - public Iterable<ApiMethodInfo> getMethods() { - return methods; + public void addMethod(ApiMethodInfo methodInfo) { + assert !hasMethod(methodInfo.name, methodInfo.descriptor) + : "method already exists " + + this.internalName + + "." + + methodInfo.name + + methodInfo.descriptor; + methods.add(methodInfo); + } + + /** Returns a read-only set. */ + public Set<ApiMethodInfo> getMethods() { + return Collections.unmodifiableSet(methods); } public boolean hasField(String fieldName) { return fields.contains(fieldName); } - public Iterable<String> getFields() { - return fields; + /** Returns a read-only set. */ + public Set<String> getFields() { + return Collections.unmodifiableSet(fields); } public void addField(String name) { - assert !hasField(name) : "field already exists: " + this.binaryName + "." + name; + assert !hasField(name) : "field already exists: " + this.internalName + "." + name; fields.add(name); } + + public ApiClassInfo copy() { + return new ApiClassInfo( + internalName, + superClass, + isInterface, + new HashSet<>(interfaces), + new HashSet<>(methods), + new HashSet<>(fields)); + } }
diff --git a/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarInfo.java b/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarInfo.java index e0296b0..ed5d5fa 100644 --- a/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarInfo.java +++ b/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarInfo.java
@@ -7,6 +7,7 @@ import com.android.tools.r8.ApiDatabaseGeneratorException; import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.references.MethodReference; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -19,11 +20,21 @@ this.classes = classes; } + public static ApiJarInfo empty() { + return new ApiJarInfo(new HashMap<>()); + } + public void addClass(ApiClassInfo info) { assert !classes.containsKey(info.getBinaryName()) : info.getBinaryName() + " already exists"; classes.put(info.getBinaryName(), info); } + public void overwriteClass(ApiClassInfo info) { + assert classes.containsKey(info.getBinaryName()) + : info.getBinaryName() + " does not exist, cannot overwrite it"; + classes.put(info.getBinaryName(), info); + } + public int size() { return classes.size(); } @@ -44,6 +55,10 @@ return getClassInfo(classReference.getBinaryName()); } + public Iterable<ApiClassInfo> getClasses() { + return classes.values(); + } + public boolean hasMethod(MethodReference methodReference) throws ApiDatabaseGeneratorException { return hasMethod( methodReference.getHolderClass().getBinaryName(),
diff --git a/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarMerging.java b/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarMerging.java new file mode 100644 index 0000000..e33d8fd --- /dev/null +++ b/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarMerging.java
@@ -0,0 +1,90 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.apimodel.jar; + +import com.android.tools.r8.ApiDatabaseGeneratorException; +import java.util.Objects; + +public class ApiJarMerging { + + /** Merges {@code additions} into {@code jarInfo} and returns {@code jarInfo}. */ + public static ApiJarInfo mergeJarInfo(ApiJarInfo jarInfo, ApiJarInfo additions) + throws ApiDatabaseGeneratorException { + for (ApiClassInfo additionClass : additions.getClasses()) { + mergeJarInfo(jarInfo, additionClass); + } + return jarInfo; + } + + /** Returns a new info, which has all the merged info of {@param infos}. */ + public static ApiJarInfo mergeJarInfos(Iterable<ApiJarInfo> infos) + throws ApiDatabaseGeneratorException { + ApiJarInfo info = ApiJarInfo.empty(); + for (var i : infos) { + mergeJarInfo(info, i); + } + return info; + } + + /** Merges {@code additionClass} into {@code jarInfo} and returns {@code jarInfo}. */ + private static void mergeJarInfo(ApiJarInfo jarInfo, ApiClassInfo additionClass) + throws ApiDatabaseGeneratorException { + String binaryName = additionClass.getBinaryName(); + ApiClassInfo originalClass = jarInfo.getClassInfo(binaryName); + if (originalClass == null) { + jarInfo.addClass(additionClass.copy()); + } else { + jarInfo.overwriteClass(mergeClass(originalClass, additionClass)); + } + } + + /** Merged {@code addition} into {@code classInfo} and returns {@code classInfo}. */ + private static ApiClassInfo mergeClass(ApiClassInfo classInfo, ApiClassInfo addition) + throws ApiDatabaseGeneratorException { + checkClassHeaderConsistency(classInfo, addition); + for (ApiMethodInfo method : addition.getMethods()) { + ApiMethodInfo existing = classInfo.getMethod(method.name, method.descriptor); + if (existing == null) { + classInfo.addMethod(method.name, method.descriptor, method.isStatic); + } else { + if (!existing.equals(method)) { + throw new ApiDatabaseGeneratorException( + "Overlapping inconsistent method " + + method.name + + method.descriptor + + " in class " + + classInfo.getBinaryName()); + } + } + } + for (String field : addition.getFields()) { + if (!classInfo.hasField(field)) { + classInfo.addField(field); + } + } + return classInfo; + } + + private static void checkClassHeaderConsistency(ApiClassInfo classInfo, ApiClassInfo addition) + throws ApiDatabaseGeneratorException { + if (classInfo.isInterface() != addition.isInterface()) { + throw new ApiDatabaseGeneratorException( + "Inconsistent isInterface for class " + classInfo.getBinaryName()); + } + if (!Objects.equals(classInfo.getSuperClass(), addition.getSuperClass())) { + throw new ApiDatabaseGeneratorException( + "Inconsistent superClass for class " + + classInfo.getBinaryName() + + ": " + + classInfo.getSuperClass() + + " vs " + + addition.getSuperClass()); + } + if (!classInfo.getInterfaces().equals(addition.getInterfaces())) { + throw new ApiDatabaseGeneratorException( + "Inconsistent interfaces for class " + classInfo.getBinaryName()); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarReader.java b/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarReader.java index e7b9d4f..3c1e049 100644 --- a/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarReader.java +++ b/src/main/java/com/android/tools/r8/apimodel/jar/ApiJarReader.java
@@ -11,7 +11,6 @@ import java.io.InputStream; import java.nio.file.Path; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -24,32 +23,29 @@ public class ApiJarReader { - public static ApiJarInfo read(Collection<Path> jarPaths) - throws ApiDatabaseGeneratorException, IOException { + public static ApiJarInfo read(Path jarPath) throws ApiDatabaseGeneratorException, IOException { Map<String, ApiClassInfo> classes = new HashMap<>(); - readClasses( - jarPaths, + readClass( + jarPath, (classInfo) -> { if (classes.containsKey(classInfo.getBinaryName())) { throw new ApiDatabaseGeneratorException( - "Duplicate classes found in JARs: " + classInfo.getBinaryName()); + "Duplicate classes found in JAR: " + jarPath + ", " + classInfo.getBinaryName()); } classes.put(classInfo.getBinaryName(), classInfo); }); return new ApiJarInfo(classes); } - private static <E extends Throwable> void readClasses( - Collection<Path> jarPaths, ThrowingConsumer<ApiClassInfo, E> handler) throws E, IOException { - for (Path jarPath : jarPaths) { - ZipUtils.iterWithZipFileAndInputStream( - jarPath, - (zip, entry, input) -> { - if (ZipUtils.isClassFile(entry.getName())) { - handler.accept(readClass(input)); - } - }); - } + private static <E extends Throwable> void readClass( + Path jarPath, ThrowingConsumer<ApiClassInfo, E> handler) throws IOException, E { + ZipUtils.iterWithZipFileAndInputStream( + jarPath, + (zip, entry, input) -> { + if (ZipUtils.isClassFile(entry.getName())) { + handler.accept(readClass(input)); + } + }); } private static ApiClassInfo readClass(InputStream input) throws IOException { @@ -58,13 +54,12 @@ reader.accept( visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - return new ApiClassInfo( - visitor.className, - visitor.superName, - visitor.interfaces, - visitor.isInterface, - visitor.methods, - visitor.fields); + ApiClassInfo classInfo = + new ApiClassInfo(visitor.className, visitor.superName, visitor.isInterface); + visitor.interfaces.forEach(classInfo::addInterface); + visitor.methods.forEach(classInfo::addMethod); + visitor.fields.forEach(classInfo::addField); + return classInfo; } private static class JarClassVisitor extends ClassVisitor {
diff --git a/src/main/java/com/android/tools/r8/apimodel/jar/ApiMethodInfo.java b/src/main/java/com/android/tools/r8/apimodel/jar/ApiMethodInfo.java index 399d101..24ad464 100644 --- a/src/main/java/com/android/tools/r8/apimodel/jar/ApiMethodInfo.java +++ b/src/main/java/com/android/tools/r8/apimodel/jar/ApiMethodInfo.java
@@ -8,12 +8,12 @@ public class ApiMethodInfo { public final String name; - public final String desc; + public final String descriptor; public final boolean isStatic; - public ApiMethodInfo(String name, String desc, boolean isStatic) { + public ApiMethodInfo(String name, String descriptor, boolean isStatic) { this.name = name; - this.desc = desc; + this.descriptor = descriptor; this.isStatic = isStatic; } @@ -27,12 +27,12 @@ } ApiMethodInfo methodSig = (ApiMethodInfo) o; return name.equals(methodSig.name) - && desc.equals(methodSig.desc) + && descriptor.equals(methodSig.descriptor) && isStatic == methodSig.isStatic; } @Override public int hashCode() { - return Objects.hash(name, desc, isStatic); + return Objects.hash(name, descriptor, isStatic); } }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java index dd0e5ad..2532a28 100644 --- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java +++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -127,20 +127,16 @@ private void processClass(DexProgramClass clazz) { // Sort all annotations. - clazz.annotations().forEach(this::processAnnotation); + processAnnotationSet(clazz.annotations()); for (DexEncodedMethod method : clazz.methods()) { - method.annotations().forEach(this::processAnnotation); + processAnnotationSet(method.annotations()); method.getParameterAnnotations().forEach(this::processAnnotationSet); } for (DexEncodedField field : clazz.fields()) { - field.annotations().forEach(this::processAnnotation); + processAnnotationSet(field.annotations()); } } - private void processAnnotation(DexAnnotation annotation) { - annotation.getAnnotation().sort(); - } - private void processAnnotationSet(DexAnnotationSet annotationSet) { annotationSet.sort(namingLens); }
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java index 6aacf26..0dd8e1f 100644 --- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java +++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -243,9 +243,9 @@ } public static int getMaxNumberOfTypes(InternalOptions options) { - return options.canHaveTypeIdOver32KIssue() && options.canUseMultidex() + return options.canHaveTypeIdOver32KIssue() && !options.alwaysAllow64KTypeIds ? MAX_ENTRIES_ONLY_32K - : MAX_ENTRIES; + : MAX_ENTRIES_ONLY_65535; } public int getMaxNumberOfTypes() {
diff --git a/src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java b/src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java index eda8647..93864e4 100644 --- a/src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java +++ b/src/main/java/com/android/tools/r8/graph/CachedHashValueDexItem.java
@@ -40,9 +40,7 @@ return true; } - @Override public void flushCachedValues() { - super.flushCachedValues(); hash = NOT_COMPUTED_HASH_VALUE; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java index 19fbc65..e11409d 100644 --- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java +++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -33,7 +33,7 @@ private static final DexAnnotationSet THE_EMPTY_ANNOTATIONS_SET = new DexAnnotationSet(); public final DexAnnotation[] annotations; - private int sorted = UNSORTED; + private volatile int sorted = UNSORTED; private static void specify(StructuralSpecification<DexAnnotationSet, ?> spec) { spec.withItemArray(a -> a.annotations); @@ -139,17 +139,21 @@ } public void sort(NamingLens namingLens) { - if (sorted != UNSORTED) { - assert sorted == sortedHashCode(); - return; + if (sorted == UNSORTED) { + synchronized (this) { + if (sorted == UNSORTED) { + Arrays.sort( + annotations, + (a, b) -> a.annotation.type.compareToWithNamingLens(b.annotation.type, namingLens)); + for (DexAnnotation annotation : annotations) { + annotation.annotation.sort(); + } + flushCachedValues(); + sorted = sortedHashCode(); + } + } } - Arrays.sort( - annotations, - (a, b) -> a.annotation.type.compareToWithNamingLens(b.annotation.type, namingLens)); - for (DexAnnotation annotation : annotations) { - annotation.annotation.sort(); - } - sorted = hashCode(); + assert sorted == sortedHashCode(); } public boolean hasAnnotation(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItem.java b/src/main/java/com/android/tools/r8/graph/DexItem.java index 928673f..ee1a6ce 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItem.java +++ b/src/main/java/com/android/tools/r8/graph/DexItem.java
@@ -35,10 +35,6 @@ protected abstract void collectMixedSectionItems(MixedSectionCollection collection); - protected void flushCachedValues() { - // Overwritten in subclasses. - } - public String toSmaliString() { return toString(); } @@ -46,5 +42,4 @@ public String toSourceString() { return toString(); } - }
diff --git a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java index fe16b49..ce5a8a6 100644 --- a/src/main/java/com/android/tools/r8/graph/ProgramMethod.java +++ b/src/main/java/com/android/tools/r8/graph/ProgramMethod.java
@@ -58,7 +58,8 @@ RewrittenPrototypeDescription protoChanges = RewrittenPrototypeDescription.none(); if (methodProcessor.shouldApplyCodeRewritings(this)) { codeLens = getDefinition().getCode().getCodeLens(appView); - protoChanges = appView.graphLens().lookupPrototypeChangesForMethodDefinition(getReference()); + protoChanges = + appView.graphLens().lookupPrototypeChangesForMethodDefinition(getReference(), codeLens); } return code.buildInliningIR( context,
diff --git a/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java index 26376cd..5f37239 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/DefaultNonIdentityGraphLens.java
@@ -89,6 +89,7 @@ return RewrittenPrototypeDescription.none(); } DexMethod previousMethodSignature = getPreviousMethodSignature(method); - return getPrevious().lookupPrototypeChangesForMethodDefinition(previousMethodSignature); + return getPrevious() + .lookupPrototypeChangesForMethodDefinition(previousMethodSignature, codeLens); } }
diff --git a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java index 4c45082..608135b 100644 --- a/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java +++ b/src/main/java/com/android/tools/r8/graph/lens/GraphLens.java
@@ -338,6 +338,8 @@ MethodLookupResult lookupMethod(MethodLookupResult previous); } + @Deprecated + @SuppressWarnings("InlineMeSuggester") public final RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition( DexMethod method) { return lookupPrototypeChangesForMethodDefinition(method, null); @@ -359,6 +361,8 @@ return lookupFieldResult(field, codeLens).getReference(); } + @Deprecated + @SuppressWarnings("InlineMeSuggester") /** Lookup a rebound or non-rebound field reference using the current graph lens. */ public final FieldLookupResult lookupFieldResult(DexField field) { // Lookup the field using the graph lens and return the lookup result.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java index 2892bef..4dc3882 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1729,6 +1729,205 @@ new StatifyingMethodGenerator( method, BackportedMethods::StringMethods_stripTrailing, "stripTrailing", type)); } + + // java.util.Arrays. + { + DexString name = factory.createString("compareUnsigned"); + + class ArrayCompareUnsignedMethodInfo { + private final DexType arrayType; + private final TemplateMethodFactory provider; + private final TemplateMethodFactory providerRange; + + private ArrayCompareUnsignedMethodInfo( + DexType arrayType, + TemplateMethodFactory provider, + TemplateMethodFactory providerRange) { + this.arrayType = arrayType; + this.provider = provider; + this.providerRange = providerRange; + } + } + + ImmutableList.Builder<ArrayCompareUnsignedMethodInfo> builder = ImmutableList.builder(); + builder.add( + new ArrayCompareUnsignedMethodInfo( + factory.byteArrayType, + BackportedMethods::ArraysMethods_compareUnsignedByte, + BackportedMethods::ArraysMethods_compareUnsignedByteRange)); + builder.add( + new ArrayCompareUnsignedMethodInfo( + factory.shortArrayType, + BackportedMethods::ArraysMethods_compareUnsignedShort, + BackportedMethods::ArraysMethods_compareUnsignedShortRange)); + builder.add( + new ArrayCompareUnsignedMethodInfo( + factory.intArrayType, + BackportedMethods::ArraysMethods_compareUnsignedInt, + BackportedMethods::ArraysMethods_compareUnsignedIntRange)); + builder.add( + new ArrayCompareUnsignedMethodInfo( + factory.longArrayType, + BackportedMethods::ArraysMethods_compareUnsignedLong, + BackportedMethods::ArraysMethods_compareUnsignedLongRange)); + builder + .build() + .forEach( + info -> { + DexProto proto = + factory.createProto(factory.intType, info.arrayType, info.arrayType); + DexMethod method = factory.createMethod(factory.arraysType, proto, name); + addProvider(new MethodGenerator(method, info.provider)); + + proto = + factory.createProto( + factory.intType, + info.arrayType, + factory.intType, + factory.intType, + info.arrayType, + factory.intType, + factory.intType); + method = factory.createMethod(factory.arraysType, proto, name); + addProvider(new MethodGenerator(method, info.providerRange)); + }); + } + { + DexString name = factory.createString("mismatch"); + + class ArrayMismatchMethodInfo { + private final DexType arrayType; + private final TemplateMethodFactory provider; + private final TemplateMethodFactory providerRange; + + private ArrayMismatchMethodInfo( + DexType arrayType, + TemplateMethodFactory provider, + TemplateMethodFactory providerRange) { + this.arrayType = arrayType; + this.provider = provider; + this.providerRange = providerRange; + } + } + + ImmutableList.Builder<ArrayMismatchMethodInfo> builder = ImmutableList.builder(); + builder.add( + new ArrayMismatchMethodInfo( + factory.booleanArrayType, + BackportedMethods::ArraysMethods_mismatchBoolean, + BackportedMethods::ArraysMethods_mismatchBooleanRange)); + builder.add( + new ArrayMismatchMethodInfo( + factory.byteArrayType, + BackportedMethods::ArraysMethods_mismatchByte, + BackportedMethods::ArraysMethods_mismatchByteRange)); + builder.add( + new ArrayMismatchMethodInfo( + factory.charArrayType, + BackportedMethods::ArraysMethods_mismatchChar, + BackportedMethods::ArraysMethods_mismatchCharRange)); + builder.add( + new ArrayMismatchMethodInfo( + factory.shortArrayType, + BackportedMethods::ArraysMethods_mismatchShort, + BackportedMethods::ArraysMethods_mismatchShortRange)); + builder.add( + new ArrayMismatchMethodInfo( + factory.intArrayType, + BackportedMethods::ArraysMethods_mismatchInt, + BackportedMethods::ArraysMethods_mismatchIntRange)); + builder.add( + new ArrayMismatchMethodInfo( + factory.longArrayType, + BackportedMethods::ArraysMethods_mismatchLong, + BackportedMethods::ArraysMethods_mismatchLongRange)); + builder.add( + new ArrayMismatchMethodInfo( + factory.floatArrayType, + BackportedMethods::ArraysMethods_mismatchFloat, + BackportedMethods::ArraysMethods_mismatchFloatRange)); + builder.add( + new ArrayMismatchMethodInfo( + factory.doubleArrayType, + BackportedMethods::ArraysMethods_mismatchDouble, + BackportedMethods::ArraysMethods_mismatchDoubleRange)); + builder.add( + new ArrayMismatchMethodInfo( + factory.objectArrayType, + BackportedMethods::ArraysMethods_mismatchObject, + BackportedMethods::ArraysMethods_mismatchObjectRange)); + builder + .build() + .forEach( + info -> { + DexProto proto = + factory.createProto(factory.intType, info.arrayType, info.arrayType); + DexMethod method = factory.createMethod(factory.arraysType, proto, name); + addProvider(new MethodGenerator(method, info.provider)); + + proto = + factory.createProto( + factory.intType, + info.arrayType, + factory.intType, + factory.intType, + info.arrayType, + factory.intType, + factory.intType); + method = factory.createMethod(factory.arraysType, proto, name); + addProvider( + new MethodWithHelperGenerator( + method, + info.providerRange, + cfInvoke -> + cfInvoke.getMethod().getName().toString().equals("checkValidRange"), + appView + .dexItemFactory() + .createProto( + appView.dexItemFactory().voidType, + appView.dexItemFactory().intType, + appView.dexItemFactory().intType, + appView.dexItemFactory().intType), + BackportedMethods::ArraysMethods_checkValidRange)); + }); + + // mismatch(Object[], Object[], Comparator) + DexProto proto = + factory.createProto( + factory.intType, + factory.objectArrayType, + factory.objectArrayType, + factory.comparatorType); + DexMethod method = factory.createMethod(factory.arraysType, proto, name); + addProvider( + new MethodGenerator(method, BackportedMethods::ArraysMethods_mismatchComparator)); + + // mismatch(Object[], int, int, Object[], int, int, Comparator) + proto = + factory.createProto( + factory.intType, + factory.objectArrayType, + factory.intType, + factory.intType, + factory.objectArrayType, + factory.intType, + factory.intType, + factory.comparatorType); + method = factory.createMethod(factory.arraysType, proto, name); + addProvider( + new MethodWithHelperGenerator( + method, + BackportedMethods::ArraysMethods_mismatchComparatorRange, + cfInvoke -> cfInvoke.getMethod().getName().toString().equals("checkValidRange"), + appView + .dexItemFactory() + .createProto( + appView.dexItemFactory().voidType, + appView.dexItemFactory().intType, + appView.dexItemFactory().intType, + appView.dexItemFactory().intType), + BackportedMethods::ArraysMethods_checkValidRange)); + } } private void initializeAndroidOptionalTMethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java index fe90886..1c4cfad 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -71,6 +71,7 @@ factory.createSynthesizedType("Ljava/lang/ArithmeticException;"); factory.createSynthesizedType("Ljava/lang/ArrayIndexOutOfBoundsException;"); factory.createSynthesizedType("Ljava/lang/AssertionError;"); + factory.createSynthesizedType("Ljava/lang/Byte;"); factory.createSynthesizedType("Ljava/lang/Double;"); factory.createSynthesizedType("Ljava/lang/Exception;"); factory.createSynthesizedType("Ljava/lang/ExceptionInInitializerError;"); @@ -91,6 +92,7 @@ factory.createSynthesizedType("Ljava/lang/Runnable;"); factory.createSynthesizedType("Ljava/lang/RuntimeException;"); factory.createSynthesizedType("Ljava/lang/SecurityException;"); + factory.createSynthesizedType("Ljava/lang/Short;"); factory.createSynthesizedType("Ljava/lang/System;"); factory.createSynthesizedType("Ljava/lang/Thread;"); factory.createSynthesizedType("Ljava/lang/reflect/Constructor;"); @@ -406,6 +408,875 @@ ImmutableList.of()); } + public static CfCode ArraysMethods_compareUnsignedByte(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 3, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label2), + label1, + new CfConstNumber(0, ValueType.INT), + new CfReturn(ValueType.INT), + label2, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfIf(IfType.NE, ValueType.OBJECT, label4), + label3, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType) + })), + new CfLoad(ValueType.OBJECT, 1), + new CfIf(IfType.NE, ValueType.OBJECT, label6), + label5, + new CfConstNumber(1, ValueType.INT), + new CfReturn(ValueType.INT), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Arrays;"), + factory.createProto( + factory.intType, factory.byteArrayType, factory.byteArrayType), + factory.createString("mismatch")), + false), + new CfStore(ValueType.INT, 2), + label7, + new CfLoad(ValueType.INT, 2), + new CfIf(IfType.LT, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfIfCmp(IfType.GE, ValueType.INT, label9), + label8, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 2), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 2), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Byte;"), + factory.createProto(factory.intType, factory.byteType, factory.byteType), + factory.createString("compareUnsigned")), + false), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfReturn(ValueType.INT), + label10), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_compareUnsignedByteRange( + DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + return new CfCode( + method.holder, + 6, + 7, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Arrays;"), + factory.createProto( + factory.intType, + factory.byteArrayType, + factory.intType, + factory.intType, + factory.byteArrayType, + factory.intType, + factory.intType), + factory.createString("mismatch")), + false), + new CfStore(ValueType.INT, 6), + label1, + new CfLoad(ValueType.INT, 6), + new CfIf(IfType.LT, ValueType.INT, label3), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfIfCmp(IfType.GE, ValueType.INT, label3), + label2, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 6), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 6), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Byte;"), + factory.createProto(factory.intType, factory.byteType, factory.byteType), + factory.createString("compareUnsigned")), + false), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfReturn(ValueType.INT), + label4), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_compareUnsignedInt(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 3, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label2), + label1, + new CfConstNumber(0, ValueType.INT), + new CfReturn(ValueType.INT), + label2, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfIf(IfType.NE, ValueType.OBJECT, label4), + label3, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType) + })), + new CfLoad(ValueType.OBJECT, 1), + new CfIf(IfType.NE, ValueType.OBJECT, label6), + label5, + new CfConstNumber(1, ValueType.INT), + new CfReturn(ValueType.INT), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Arrays;"), + factory.createProto( + factory.intType, factory.intArrayType, factory.intArrayType), + factory.createString("mismatch")), + false), + new CfStore(ValueType.INT, 2), + label7, + new CfLoad(ValueType.INT, 2), + new CfIf(IfType.LT, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfIfCmp(IfType.GE, ValueType.INT, label9), + label8, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 2), + new CfArrayLoad(MemberType.INT), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 2), + new CfArrayLoad(MemberType.INT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Integer;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("compareUnsigned")), + false), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfReturn(ValueType.INT), + label10), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_compareUnsignedIntRange( + DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + return new CfCode( + method.holder, + 6, + 7, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Arrays;"), + factory.createProto( + factory.intType, + factory.intArrayType, + factory.intType, + factory.intType, + factory.intArrayType, + factory.intType, + factory.intType), + factory.createString("mismatch")), + false), + new CfStore(ValueType.INT, 6), + label1, + new CfLoad(ValueType.INT, 6), + new CfIf(IfType.LT, ValueType.INT, label3), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfIfCmp(IfType.GE, ValueType.INT, label3), + label2, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 6), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.INT), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 6), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.INT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Integer;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("compareUnsigned")), + false), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfReturn(ValueType.INT), + label4), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_compareUnsignedLong(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 3, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label2), + label1, + new CfConstNumber(0, ValueType.INT), + new CfReturn(ValueType.INT), + label2, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfIf(IfType.NE, ValueType.OBJECT, label4), + label3, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType) + })), + new CfLoad(ValueType.OBJECT, 1), + new CfIf(IfType.NE, ValueType.OBJECT, label6), + label5, + new CfConstNumber(1, ValueType.INT), + new CfReturn(ValueType.INT), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Arrays;"), + factory.createProto( + factory.intType, factory.longArrayType, factory.longArrayType), + factory.createString("mismatch")), + false), + new CfStore(ValueType.INT, 2), + label7, + new CfLoad(ValueType.INT, 2), + new CfIf(IfType.LT, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfIfCmp(IfType.GE, ValueType.INT, label9), + label8, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 2), + new CfArrayLoad(MemberType.LONG), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 2), + new CfArrayLoad(MemberType.LONG), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Long;"), + factory.createProto(factory.intType, factory.longType, factory.longType), + factory.createString("compareUnsigned")), + false), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfReturn(ValueType.INT), + label10), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_compareUnsignedLongRange( + DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + return new CfCode( + method.holder, + 6, + 7, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Arrays;"), + factory.createProto( + factory.intType, + factory.longArrayType, + factory.intType, + factory.intType, + factory.longArrayType, + factory.intType, + factory.intType), + factory.createString("mismatch")), + false), + new CfStore(ValueType.INT, 6), + label1, + new CfLoad(ValueType.INT, 6), + new CfIf(IfType.LT, ValueType.INT, label3), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfIfCmp(IfType.GE, ValueType.INT, label3), + label2, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 6), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.LONG), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 6), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.LONG), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Long;"), + factory.createProto(factory.intType, factory.longType, factory.longType), + factory.createString("compareUnsigned")), + false), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfReturn(ValueType.INT), + label4), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_compareUnsignedShort( + DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 3, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label2), + label1, + new CfConstNumber(0, ValueType.INT), + new CfReturn(ValueType.INT), + label2, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfIf(IfType.NE, ValueType.OBJECT, label4), + label3, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType) + })), + new CfLoad(ValueType.OBJECT, 1), + new CfIf(IfType.NE, ValueType.OBJECT, label6), + label5, + new CfConstNumber(1, ValueType.INT), + new CfReturn(ValueType.INT), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Arrays;"), + factory.createProto( + factory.intType, factory.shortArrayType, factory.shortArrayType), + factory.createString("mismatch")), + false), + new CfStore(ValueType.INT, 2), + label7, + new CfLoad(ValueType.INT, 2), + new CfIf(IfType.LT, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfIfCmp(IfType.GE, ValueType.INT, label9), + label8, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 2), + new CfArrayLoad(MemberType.SHORT), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 2), + new CfArrayLoad(MemberType.SHORT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Short;"), + factory.createProto(factory.intType, factory.shortType, factory.shortType), + factory.createString("compareUnsigned")), + false), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfReturn(ValueType.INT), + label10), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_compareUnsignedShortRange( + DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + return new CfCode( + method.holder, + 6, + 7, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Arrays;"), + factory.createProto( + factory.intType, + factory.shortArrayType, + factory.intType, + factory.intType, + factory.shortArrayType, + factory.intType, + factory.intType), + factory.createString("mismatch")), + false), + new CfStore(ValueType.INT, 6), + label1, + new CfLoad(ValueType.INT, 6), + new CfIf(IfType.LT, ValueType.INT, label3), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfIfCmp(IfType.GE, ValueType.INT, label3), + label2, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 6), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.SHORT), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 6), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.SHORT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Short;"), + factory.createProto(factory.intType, factory.shortType, factory.shortType), + factory.createString("compareUnsigned")), + false), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfReturn(ValueType.INT), + label4), + ImmutableList.of(), + ImmutableList.of()); + } + public static CfCode ArraysMethods_equalsBoolean(DexItemFactory factory, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); @@ -2634,6 +3505,3258 @@ ImmutableList.of()); } + public static CfCode ArraysMethods_mismatchBoolean(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label3), + label2, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 3), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 3), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(IfType.GE, ValueType.INT, label8), + label5, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfIfCmp(IfType.EQ, ValueType.INT, label7), + label6, + new CfLoad(ValueType.INT, 3), + new CfReturn(ValueType.INT), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(3, 1), + new CfGoto(label4), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfGoto(label10), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label11), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchBooleanRange( + DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 10, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label1, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 6), + label3, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 8), + label5, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 9), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.GE, ValueType.INT, label10), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + label8, + new CfLoad(ValueType.INT, 9), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(9, 1), + new CfGoto(label6), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfIfCmp(IfType.EQ, ValueType.INT, label11), + new CfLoad(ValueType.INT, 8), + new CfGoto(label12), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.booleanArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchByte(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label3), + label2, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 3), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 3), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(IfType.GE, ValueType.INT, label8), + label5, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfIfCmp(IfType.EQ, ValueType.INT, label7), + label6, + new CfLoad(ValueType.INT, 3), + new CfReturn(ValueType.INT), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(3, 1), + new CfGoto(label4), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfGoto(label10), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label11), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchByteRange(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 10, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label1, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 6), + label3, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 8), + label5, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 9), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.GE, ValueType.INT, label10), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.BOOLEAN_OR_BYTE), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + label8, + new CfLoad(ValueType.INT, 9), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(9, 1), + new CfGoto(label6), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfIfCmp(IfType.EQ, ValueType.INT, label11), + new CfLoad(ValueType.INT, 8), + new CfGoto(label12), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.byteArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchChar(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label3), + label2, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 3), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 3), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(IfType.GE, ValueType.INT, label8), + label5, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.CHAR), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.CHAR), + new CfIfCmp(IfType.EQ, ValueType.INT, label7), + label6, + new CfLoad(ValueType.INT, 3), + new CfReturn(ValueType.INT), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(3, 1), + new CfGoto(label4), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfGoto(label10), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label11), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchCharRange(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 10, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label1, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 6), + label3, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 8), + label5, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 9), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.GE, ValueType.INT, label10), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.CHAR), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.CHAR), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + label8, + new CfLoad(ValueType.INT, 9), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(9, 1), + new CfGoto(label6), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfIfCmp(IfType.EQ, ValueType.INT, label11), + new CfLoad(ValueType.INT, 8), + new CfGoto(label12), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.charArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchComparator(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 5, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Objects;"), + factory.createProto(factory.objectType, factory.objectType), + factory.createString("requireNonNull")), + false), + new CfStackInstruction(CfStackInstruction.Opcode.Pop), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 3), + label2, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label4), + label3, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 4), + label5, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 3), + new CfIfCmp(IfType.GE, ValueType.INT, label9), + label6, + new CfLoad(ValueType.OBJECT, 2), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 4), + new CfArrayLoad(MemberType.OBJECT), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 4), + new CfArrayLoad(MemberType.OBJECT), + new CfInvoke( + 185, + factory.createMethod( + factory.createType("Ljava/util/Comparator;"), + factory.createProto(factory.intType, factory.objectType, factory.objectType), + factory.createString("compare")), + true), + new CfIf(IfType.EQ, ValueType.INT, label8), + label7, + new CfLoad(ValueType.INT, 4), + new CfReturn(ValueType.INT), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(4, 1), + new CfGoto(label5), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label10), + new CfLoad(ValueType.INT, 3), + new CfGoto(label11), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label12), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchComparatorRange( + DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + CfLabel label14 = new CfLabel(); + return new CfCode( + method.holder, + 5, + 11, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 6), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Objects;"), + factory.createProto(factory.objectType, factory.objectType), + factory.createString("requireNonNull")), + false), + new CfStackInstruction(CfStackInstruction.Opcode.Pop), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label3, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 8), + label5, + new CfLoad(ValueType.INT, 7), + new CfLoad(ValueType.INT, 8), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 9), + label6, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 10), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 10), + new CfLoad(ValueType.INT, 9), + new CfIfCmp(IfType.GE, ValueType.INT, label11), + label8, + new CfLoad(ValueType.OBJECT, 6), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 10), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.OBJECT), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 10), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.OBJECT), + new CfInvoke( + 185, + factory.createMethod( + factory.createType("Ljava/util/Comparator;"), + factory.createProto(factory.intType, factory.objectType, factory.objectType), + factory.createString("compare")), + true), + new CfIf(IfType.EQ, ValueType.INT, label10), + label9, + new CfLoad(ValueType.INT, 10), + new CfReturn(ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(10, 1), + new CfGoto(label7), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 7), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.EQ, ValueType.INT, label12), + new CfLoad(ValueType.INT, 9), + new CfGoto(label13), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label13, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("Ljava/util/Comparator;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label14), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchDouble(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label3), + label2, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 3), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 3), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(IfType.GE, ValueType.INT, label8), + label5, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.DOUBLE), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Double;"), + factory.createProto(factory.longType, factory.doubleType), + factory.createString("doubleToLongBits")), + false), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.DOUBLE), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Double;"), + factory.createProto(factory.longType, factory.doubleType), + factory.createString("doubleToLongBits")), + false), + new CfCmp(Cmp.Bias.NONE, NumericType.LONG), + new CfIf(IfType.EQ, ValueType.INT, label7), + label6, + new CfLoad(ValueType.INT, 3), + new CfReturn(ValueType.INT), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(3, 1), + new CfGoto(label4), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfGoto(label10), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label11), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchDoubleRange(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 5, + 10, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label1, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 6), + label3, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 8), + label5, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 9), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.GE, ValueType.INT, label10), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.DOUBLE), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Double;"), + factory.createProto(factory.longType, factory.doubleType), + factory.createString("doubleToLongBits")), + false), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.DOUBLE), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Double;"), + factory.createProto(factory.longType, factory.doubleType), + factory.createString("doubleToLongBits")), + false), + new CfCmp(Cmp.Bias.NONE, NumericType.LONG), + new CfIf(IfType.EQ, ValueType.INT, label9), + label8, + new CfLoad(ValueType.INT, 9), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(9, 1), + new CfGoto(label6), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfIfCmp(IfType.EQ, ValueType.INT, label11), + new CfLoad(ValueType.INT, 8), + new CfGoto(label12), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.doubleArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchFloat(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label3), + label2, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 3), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 3), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(IfType.GE, ValueType.INT, label8), + label5, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.FLOAT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Float;"), + factory.createProto(factory.intType, factory.floatType), + factory.createString("floatToIntBits")), + false), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.FLOAT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Float;"), + factory.createProto(factory.intType, factory.floatType), + factory.createString("floatToIntBits")), + false), + new CfIfCmp(IfType.EQ, ValueType.INT, label7), + label6, + new CfLoad(ValueType.INT, 3), + new CfReturn(ValueType.INT), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(3, 1), + new CfGoto(label4), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfGoto(label10), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label11), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchFloatRange(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 10, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label1, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 6), + label3, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 8), + label5, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 9), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.GE, ValueType.INT, label10), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.FLOAT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Float;"), + factory.createProto(factory.intType, factory.floatType), + factory.createString("floatToIntBits")), + false), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.FLOAT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Float;"), + factory.createProto(factory.intType, factory.floatType), + factory.createString("floatToIntBits")), + false), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + label8, + new CfLoad(ValueType.INT, 9), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(9, 1), + new CfGoto(label6), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfIfCmp(IfType.EQ, ValueType.INT, label11), + new CfLoad(ValueType.INT, 8), + new CfGoto(label12), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.floatArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchInt(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label3), + label2, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 3), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 3), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(IfType.GE, ValueType.INT, label8), + label5, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.INT), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.INT), + new CfIfCmp(IfType.EQ, ValueType.INT, label7), + label6, + new CfLoad(ValueType.INT, 3), + new CfReturn(ValueType.INT), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(3, 1), + new CfGoto(label4), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfGoto(label10), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label11), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchIntRange(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 10, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label1, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 6), + label3, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 8), + label5, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 9), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.GE, ValueType.INT, label10), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.INT), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.INT), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + label8, + new CfLoad(ValueType.INT, 9), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(9, 1), + new CfGoto(label6), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfIfCmp(IfType.EQ, ValueType.INT, label11), + new CfLoad(ValueType.INT, 8), + new CfGoto(label12), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.intArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchLong(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label3), + label2, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 3), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 3), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(IfType.GE, ValueType.INT, label8), + label5, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.LONG), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.LONG), + new CfCmp(Cmp.Bias.NONE, NumericType.LONG), + new CfIf(IfType.EQ, ValueType.INT, label7), + label6, + new CfLoad(ValueType.INT, 3), + new CfReturn(ValueType.INT), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(3, 1), + new CfGoto(label4), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfGoto(label10), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label11), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchLongRange(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 5, + 10, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label1, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 6), + label3, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 8), + label5, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 9), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.GE, ValueType.INT, label10), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.LONG), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.LONG), + new CfCmp(Cmp.Bias.NONE, NumericType.LONG), + new CfIf(IfType.EQ, ValueType.INT, label9), + label8, + new CfLoad(ValueType.INT, 9), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(9, 1), + new CfGoto(label6), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfIfCmp(IfType.EQ, ValueType.INT, label11), + new CfLoad(ValueType.INT, 8), + new CfGoto(label12), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.longArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchObject(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label3), + label2, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 3), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 3), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(IfType.GE, ValueType.INT, label8), + label5, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.OBJECT), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.OBJECT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Objects;"), + factory.createProto( + factory.booleanType, factory.objectType, factory.objectType), + factory.createString("equals")), + false), + new CfIf(IfType.NE, ValueType.INT, label7), + label6, + new CfLoad(ValueType.INT, 3), + new CfReturn(ValueType.INT), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(3, 1), + new CfGoto(label4), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfGoto(label10), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label11), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchObjectRange(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 10, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label1, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 6), + label3, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 8), + label5, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 9), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.GE, ValueType.INT, label10), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.OBJECT), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.OBJECT), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/util/Objects;"), + factory.createProto( + factory.booleanType, factory.objectType, factory.objectType), + factory.createString("equals")), + false), + new CfIf(IfType.NE, ValueType.INT, label9), + label8, + new CfLoad(ValueType.INT, 9), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(9, 1), + new CfGoto(label6), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfIfCmp(IfType.EQ, ValueType.INT, label11), + new CfLoad(ValueType.INT, 8), + new CfGoto(label12), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference( + factory.createType("[Ljava/lang/Object;")), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchShort(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 4, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 1), + new CfIfCmp(IfType.NE, ValueType.OBJECT, label3), + label2, + new CfConstNumber(-1, ValueType.INT), + new CfReturn(ValueType.INT), + label3, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType() + })), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 3), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 3), + new CfLoad(ValueType.INT, 2), + new CfIfCmp(IfType.GE, ValueType.INT, label8), + label5, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.SHORT), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 3), + new CfArrayLoad(MemberType.SHORT), + new CfIfCmp(IfType.EQ, ValueType.INT, label7), + label6, + new CfLoad(ValueType.INT, 3), + new CfReturn(ValueType.INT), + label7, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(3, 1), + new CfGoto(label4), + label8, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.OBJECT, 1), + new CfArrayLength(), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + new CfLoad(ValueType.INT, 2), + new CfGoto(label10), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label11), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode ArraysMethods_mismatchShortRange(DexItemFactory factory, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); + CfLabel label12 = new CfLabel(); + CfLabel label13 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 10, + ImmutableList.of( + label0, + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label1, + new CfLoad(ValueType.OBJECT, 3), + new CfArrayLength(), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 5), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/ir/desugar/backports/ArraysMethods;"), + factory.createProto( + factory.voidType, factory.intType, factory.intType, factory.intType), + factory.createString("checkValidRange")), + false), + label2, + new CfLoad(ValueType.INT, 2), + new CfLoad(ValueType.INT, 1), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 6), + label3, + new CfLoad(ValueType.INT, 5), + new CfLoad(ValueType.INT, 4), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT), + new CfStore(ValueType.INT, 7), + label4, + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Math;"), + factory.createProto(factory.intType, factory.intType, factory.intType), + factory.createString("min")), + false), + new CfStore(ValueType.INT, 8), + label5, + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 9), + label6, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 9), + new CfLoad(ValueType.INT, 8), + new CfIfCmp(IfType.GE, ValueType.INT, label10), + label7, + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.INT, 1), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.SHORT), + new CfLoad(ValueType.OBJECT, 3), + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 9), + new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT), + new CfArrayLoad(MemberType.SHORT), + new CfIfCmp(IfType.EQ, ValueType.INT, label9), + label8, + new CfLoad(ValueType.INT, 9), + new CfReturn(ValueType.INT), + label9, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfIinc(9, 1), + new CfGoto(label6), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 6), + new CfLoad(ValueType.INT, 7), + new CfIfCmp(IfType.EQ, ValueType.INT, label11), + new CfLoad(ValueType.INT, 8), + new CfGoto(label12), + label11, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + })), + new CfConstNumber(-1, ValueType.INT), + label12, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8}, + new FrameType[] { + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.initializedNonNullReference(factory.shortArrayType), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType(), + FrameType.intType() + }), + new ArrayDeque<>(Arrays.asList(FrameType.intType()))), + new CfReturn(ValueType.INT), + label13), + ImmutableList.of(), + ImmutableList.of()); + } + public static CfCode AssertionErrorMethods_createAssertionError( DexItemFactory factory, DexMethod method) { CfLabel label0 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java index 283d254..8c57a9b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/code/CheckNotZeroCode.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.UseRegistry; +import com.android.tools.r8.graph.lens.GraphLens; import com.android.tools.r8.ir.analysis.type.TypeElement; import com.android.tools.r8.ir.code.Argument; import com.android.tools.r8.ir.code.IRCode; @@ -57,20 +58,21 @@ .setIsD8R8Synthesized(checkNotZeroMethod.getDefinition().isD8R8Synthesized()) .build(); NumberGenerator valueNumberGenerator = new NumberGenerator(); + + Code checkNotNullMethodCode = checkNotNullMethod.getDefinition().getCode(); + GraphLens codeLens = checkNotNullMethodCode.getCodeLens(appView); IRCode code = - checkNotNullMethod - .getDefinition() - .getCode() - .buildInliningIR( - checkNotZeroMethod, - checkNotNullMethod, - appView, - appView.graphLens(), - valueNumberGenerator, - callerPosition, - appView - .graphLens() - .lookupPrototypeChangesForMethodDefinition(checkNotNullMethod.getReference())); + checkNotNullMethodCode.buildInliningIR( + checkNotZeroMethod, + checkNotNullMethod, + appView, + appView.graphLens(), + valueNumberGenerator, + callerPosition, + appView + .graphLens() + .lookupPrototypeChangesForMethodDefinition( + checkNotNullMethod.getReference(), codeLens)); InstructionListIterator instructionIterator = code.instructionListIterator(); // Start iterating at the argument instruction for the checked argument.
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java index 3baed60..ec48f6f 100644 --- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java +++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -114,6 +114,10 @@ return null; } + public MergedClassesInformation asMergedClassesInformation() { + return null; + } + public boolean shouldCompose(MappingInformation existing) { return !allowOther(existing); } @@ -192,7 +196,7 @@ PartitionFileNameInformation.deserialize(object, onMappingInfo); return; case MergedClassesInformation.ID: - MergedClassesInformation.deserialize(version, onMappingInfo); + MergedClassesInformation.deserialize(version, object, onMappingInfo); return; default: diagnosticsHandler.info(MappingInformationDiagnostics.noHandlerFor(lineNumber, id));
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MergedClassesInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MergedClassesInformation.java index edefd02..0a81a8f 100644 --- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MergedClassesInformation.java +++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MergedClassesInformation.java
@@ -15,7 +15,11 @@ public static final String ID = "com.android.tools.r8.mergedClasses"; public static final MapVersion SUPPORTED_VERSION = MapVersion.MAP_VERSION_2_3; - private MergedClassesInformation() {} + private final JsonObject rawObject; + + private MergedClassesInformation(JsonObject rawObject) { + this.rawObject = rawObject; + } @Override public String getId() { @@ -37,20 +41,35 @@ return true; } + @Override + public MergedClassesInformation asMergedClassesInformation() { + return this; + } + + public static MergedClassesInformation build(JsonObject rawObject) { + return new MergedClassesInformation(rawObject); + } + public static MergedClassesInformation build() { - return new MergedClassesInformation(); + JsonObject object = new JsonObject(); + object.add(MAPPING_ID_KEY, new JsonPrimitive(ID)); + return new MergedClassesInformation(object); } @Override public String serialize() { + if (rawObject != null) { + return rawObject.toString(); + } JsonObject object = new JsonObject(); object.add(MAPPING_ID_KEY, new JsonPrimitive(ID)); return object.toString(); } - public static void deserialize(MapVersion version, Consumer<MappingInformation> onMappingInfo) { + public static void deserialize( + MapVersion version, JsonObject object, Consumer<MappingInformation> onMappingInfo) { if (isSupported(version)) { - onMappingInfo.accept(new MergedClassesInformation()); + onMappingInfo.accept(new MergedClassesInformation(object)); } }
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java index 23a782f..d33092b 100644 --- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java +++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizerGraphLens.java
@@ -54,7 +54,7 @@ } DexMethod previousMethodSignature = getPreviousMethodSignature(method); RewrittenPrototypeDescription previousPrototypeChanges = - getPrevious().lookupPrototypeChangesForMethodDefinition(previousMethodSignature); + getPrevious().lookupPrototypeChangesForMethodDefinition(previousMethodSignature, codeLens); if (previousMethodSignature == method) { return previousPrototypeChanges; }
diff --git a/src/main/java/com/android/tools/r8/utils/CliParserUtils.java b/src/main/java/com/android/tools/r8/utils/CliParserUtils.java index ce3f94c..f6ddcac 100644 --- a/src/main/java/com/android/tools/r8/utils/CliParserUtils.java +++ b/src/main/java/com/android/tools/r8/utils/CliParserUtils.java
@@ -32,8 +32,8 @@ List<String> helpLines = StringUtils.wrapToWidth(info.description, descriptionWidth); List<String> alternatives; if (info.shorthand != null) { - assert info.suffixLabel == null : "shorthands and prefixes cannot be combined."; - alternatives = ImmutableList.of(commandString(info.shorthand, null, info.paramLabels)); + alternatives = + ImmutableList.of(commandString(info.shorthand, info.suffixLabel, info.paramLabels)); } else { alternatives = ImmutableList.of(); }
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 1c47571..cb5727b 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -613,6 +613,9 @@ public boolean disableLambdaMethodAnnotations = System.getProperty("com.android.tools.r8.disableLambdaMethodAnnotations") != null; + // TODO(b/547484295): Remove this flag. + public boolean alwaysAllow64KTypeIds = + System.getProperty("com.android.tools.r8.alwaysAllow64KTypeIds") != null; // TODO(b/548528931): Remove this flag. public boolean alwaysAllow64KFieldIds = System.getProperty("com.android.tools.r8.alwaysAllow64KFieldIds") != null;
diff --git a/src/main/resources/resources/hidden.api.txt b/src/main/resources/resources/hidden.api.txt new file mode 100644 index 0000000..1bc2961 --- /dev/null +++ b/src/main/resources/resources/hidden.api.txt
@@ -0,0 +1,143 @@ +// Copyright (c) 2026, 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. +// +// This file is generated by GenerateApiAmendments.java. +// Do not edit manually. +class Lsun/misc/Unsafe; 1 +field Lsun/misc/Unsafe; INVALID_FIELD_OFFSET 24 +method Ljava/nio/ByteBuffer; clear ()Ljava/nio/ByteBuffer; 28 +method Ljava/nio/ByteBuffer; flip ()Ljava/nio/ByteBuffer; 28 +method Ljava/nio/ByteBuffer; limit (I)Ljava/nio/ByteBuffer; 28 +method Ljava/nio/ByteBuffer; mark ()Ljava/nio/ByteBuffer; 28 +method Ljava/nio/ByteBuffer; position (I)Ljava/nio/ByteBuffer; 28 +method Ljava/nio/ByteBuffer; reset ()Ljava/nio/ByteBuffer; 28 +method Ljava/nio/ByteBuffer; rewind ()Ljava/nio/ByteBuffer; 28 +method Ljava/nio/CharBuffer; clear ()Ljava/nio/CharBuffer; 28 +method Ljava/nio/CharBuffer; flip ()Ljava/nio/CharBuffer; 28 +method Ljava/nio/CharBuffer; limit (I)Ljava/nio/CharBuffer; 28 +method Ljava/nio/CharBuffer; mark ()Ljava/nio/CharBuffer; 28 +method Ljava/nio/CharBuffer; position (I)Ljava/nio/CharBuffer; 28 +method Ljava/nio/CharBuffer; reset ()Ljava/nio/CharBuffer; 28 +method Ljava/nio/CharBuffer; rewind ()Ljava/nio/CharBuffer; 28 +method Ljava/nio/DoubleBuffer; clear ()Ljava/nio/DoubleBuffer; 28 +method Ljava/nio/DoubleBuffer; flip ()Ljava/nio/DoubleBuffer; 28 +method Ljava/nio/DoubleBuffer; limit (I)Ljava/nio/DoubleBuffer; 28 +method Ljava/nio/DoubleBuffer; mark ()Ljava/nio/DoubleBuffer; 28 +method Ljava/nio/DoubleBuffer; position (I)Ljava/nio/DoubleBuffer; 28 +method Ljava/nio/DoubleBuffer; reset ()Ljava/nio/DoubleBuffer; 28 +method Ljava/nio/DoubleBuffer; rewind ()Ljava/nio/DoubleBuffer; 28 +method Ljava/nio/FloatBuffer; clear ()Ljava/nio/FloatBuffer; 28 +method Ljava/nio/FloatBuffer; flip ()Ljava/nio/FloatBuffer; 28 +method Ljava/nio/FloatBuffer; limit (I)Ljava/nio/FloatBuffer; 28 +method Ljava/nio/FloatBuffer; mark ()Ljava/nio/FloatBuffer; 28 +method Ljava/nio/FloatBuffer; position (I)Ljava/nio/FloatBuffer; 28 +method Ljava/nio/FloatBuffer; reset ()Ljava/nio/FloatBuffer; 28 +method Ljava/nio/FloatBuffer; rewind ()Ljava/nio/FloatBuffer; 28 +method Ljava/nio/IntBuffer; clear ()Ljava/nio/IntBuffer; 28 +method Ljava/nio/IntBuffer; flip ()Ljava/nio/IntBuffer; 28 +method Ljava/nio/IntBuffer; limit (I)Ljava/nio/IntBuffer; 28 +method Ljava/nio/IntBuffer; mark ()Ljava/nio/IntBuffer; 28 +method Ljava/nio/IntBuffer; position (I)Ljava/nio/IntBuffer; 28 +method Ljava/nio/IntBuffer; reset ()Ljava/nio/IntBuffer; 28 +method Ljava/nio/IntBuffer; rewind ()Ljava/nio/IntBuffer; 28 +method Ljava/nio/LongBuffer; clear ()Ljava/nio/LongBuffer; 28 +method Ljava/nio/LongBuffer; flip ()Ljava/nio/LongBuffer; 28 +method Ljava/nio/LongBuffer; limit (I)Ljava/nio/LongBuffer; 28 +method Ljava/nio/LongBuffer; mark ()Ljava/nio/LongBuffer; 28 +method Ljava/nio/LongBuffer; position (I)Ljava/nio/LongBuffer; 28 +method Ljava/nio/LongBuffer; reset ()Ljava/nio/LongBuffer; 28 +method Ljava/nio/LongBuffer; rewind ()Ljava/nio/LongBuffer; 28 +method Ljava/nio/MappedByteBuffer; clear ()Ljava/nio/ByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; clear ()Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; compact ()Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; duplicate ()Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; flip ()Ljava/nio/ByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; flip ()Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; limit (I)Ljava/nio/ByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; limit (I)Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; mark ()Ljava/nio/ByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; mark ()Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; position (I)Ljava/nio/ByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; position (I)Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; reset ()Ljava/nio/ByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; reset ()Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; rewind ()Ljava/nio/ByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; rewind ()Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/MappedByteBuffer; slice ()Ljava/nio/MappedByteBuffer; 34 +method Ljava/nio/ShortBuffer; clear ()Ljava/nio/ShortBuffer; 28 +method Ljava/nio/ShortBuffer; flip ()Ljava/nio/ShortBuffer; 28 +method Ljava/nio/ShortBuffer; limit (I)Ljava/nio/ShortBuffer; 28 +method Ljava/nio/ShortBuffer; mark ()Ljava/nio/ShortBuffer; 28 +method Ljava/nio/ShortBuffer; position (I)Ljava/nio/ShortBuffer; 28 +method Ljava/nio/ShortBuffer; reset ()Ljava/nio/ShortBuffer; 28 +method Ljava/nio/ShortBuffer; rewind ()Ljava/nio/ShortBuffer; 28 +method Ljava/time/LocalDate; getEra ()Ljava/time/chrono/IsoEra; 33 +method Ljava/util/concurrent/ConcurrentHashMap; keySet ()Ljava/util/concurrent/ConcurrentHashMap$KeySetView; 28 +method Lsun/misc/Unsafe; addressSize ()I 24 +method Lsun/misc/Unsafe; allocateInstance (Ljava/lang/Class;)Ljava/lang/Object; 16 +method Lsun/misc/Unsafe; allocateMemory (J)J 24 +method Lsun/misc/Unsafe; arrayBaseOffset (Ljava/lang/Class;)I 1 +method Lsun/misc/Unsafe; arrayIndexScale (Ljava/lang/Class;)I 1 +method Lsun/misc/Unsafe; compareAndSwapInt (Ljava/lang/Object;JII)Z 1 +method Lsun/misc/Unsafe; compareAndSwapLong (Ljava/lang/Object;JJJ)Z 1 +method Lsun/misc/Unsafe; compareAndSwapObject (Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z 1 +method Lsun/misc/Unsafe; copyMemory (JJJ)V 24 +method Lsun/misc/Unsafe; copyMemoryFromPrimitiveArray (Ljava/lang/Object;JJJ)V 24 +method Lsun/misc/Unsafe; copyMemoryToPrimitiveArray (JLjava/lang/Object;JJ)V 24 +method Lsun/misc/Unsafe; freeMemory (J)V 24 +method Lsun/misc/Unsafe; fullFence ()V 24 +method Lsun/misc/Unsafe; getAndAddInt (Ljava/lang/Object;JI)I 24 +method Lsun/misc/Unsafe; getAndAddLong (Ljava/lang/Object;JJ)J 24 +method Lsun/misc/Unsafe; getAndSetInt (Ljava/lang/Object;JI)I 24 +method Lsun/misc/Unsafe; getAndSetLong (Ljava/lang/Object;JJ)J 24 +method Lsun/misc/Unsafe; getAndSetObject (Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object; 24 +method Lsun/misc/Unsafe; getBoolean (Ljava/lang/Object;J)Z 24 +method Lsun/misc/Unsafe; getByte (J)B 24 +method Lsun/misc/Unsafe; getByte (Ljava/lang/Object;J)B 24 +method Lsun/misc/Unsafe; getChar (J)C 24 +method Lsun/misc/Unsafe; getChar (Ljava/lang/Object;J)C 24 +method Lsun/misc/Unsafe; getDouble (J)D 24 +method Lsun/misc/Unsafe; getDouble (Ljava/lang/Object;J)D 24 +method Lsun/misc/Unsafe; getFloat (J)F 24 +method Lsun/misc/Unsafe; getFloat (Ljava/lang/Object;J)F 24 +method Lsun/misc/Unsafe; getInt (J)I 24 +method Lsun/misc/Unsafe; getInt (Ljava/lang/Object;J)I 1 +method Lsun/misc/Unsafe; getIntVolatile (Ljava/lang/Object;J)I 1 +method Lsun/misc/Unsafe; getLong (J)J 24 +method Lsun/misc/Unsafe; getLong (Ljava/lang/Object;J)J 1 +method Lsun/misc/Unsafe; getLongVolatile (Ljava/lang/Object;J)J 1 +method Lsun/misc/Unsafe; getObject (Ljava/lang/Object;J)Ljava/lang/Object; 1 +method Lsun/misc/Unsafe; getObjectVolatile (Ljava/lang/Object;J)Ljava/lang/Object; 1 +method Lsun/misc/Unsafe; getShort (J)S 24 +method Lsun/misc/Unsafe; getShort (Ljava/lang/Object;J)S 24 +method Lsun/misc/Unsafe; getUnsafe ()Lsun/misc/Unsafe; 1 +method Lsun/misc/Unsafe; loadFence ()V 24 +method Lsun/misc/Unsafe; objectFieldOffset (Ljava/lang/reflect/Field;)J 1 +method Lsun/misc/Unsafe; pageSize ()I 24 +method Lsun/misc/Unsafe; park (ZJ)V 1 +method Lsun/misc/Unsafe; putBoolean (Ljava/lang/Object;JZ)V 24 +method Lsun/misc/Unsafe; putByte (JB)V 24 +method Lsun/misc/Unsafe; putByte (Ljava/lang/Object;JB)V 24 +method Lsun/misc/Unsafe; putChar (JC)V 24 +method Lsun/misc/Unsafe; putChar (Ljava/lang/Object;JC)V 24 +method Lsun/misc/Unsafe; putDouble (JD)V 24 +method Lsun/misc/Unsafe; putDouble (Ljava/lang/Object;JD)V 24 +method Lsun/misc/Unsafe; putFloat (JF)V 24 +method Lsun/misc/Unsafe; putFloat (Ljava/lang/Object;JF)V 24 +method Lsun/misc/Unsafe; putInt (JI)V 24 +method Lsun/misc/Unsafe; putInt (Ljava/lang/Object;JI)V 1 +method Lsun/misc/Unsafe; putIntVolatile (Ljava/lang/Object;JI)V 1 +method Lsun/misc/Unsafe; putLong (JJ)V 24 +method Lsun/misc/Unsafe; putLong (Ljava/lang/Object;JJ)V 1 +method Lsun/misc/Unsafe; putLongVolatile (Ljava/lang/Object;JJ)V 1 +method Lsun/misc/Unsafe; putObject (Ljava/lang/Object;JLjava/lang/Object;)V 1 +method Lsun/misc/Unsafe; putObjectVolatile (Ljava/lang/Object;JLjava/lang/Object;)V 1 +method Lsun/misc/Unsafe; putOrderedInt (Ljava/lang/Object;JI)V 1 +method Lsun/misc/Unsafe; putOrderedLong (Ljava/lang/Object;JJ)V 1 +method Lsun/misc/Unsafe; putOrderedObject (Ljava/lang/Object;JLjava/lang/Object;)V 1 +method Lsun/misc/Unsafe; putShort (JS)V 24 +method Lsun/misc/Unsafe; putShort (Ljava/lang/Object;JS)V 24 +method Lsun/misc/Unsafe; setMemory (JJB)V 24 +method Lsun/misc/Unsafe; storeFence ()V 24 +method Lsun/misc/Unsafe; unpark (Ljava/lang/Object;)V 1
diff --git a/src/main/resources/resources/hidden.jar.txt b/src/main/resources/resources/hidden.jar.txt new file mode 100644 index 0000000..e640fc5 --- /dev/null +++ b/src/main/resources/resources/hidden.jar.txt
@@ -0,0 +1,143 @@ +// Copyright (c) 2026, 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. +// +// This file is generated by GenerateApiAmendments.java. +// Do not edit manually. +class Lsun/misc/Unsafe; extends Ljava/lang/Object; implements none +field Lsun/misc/Unsafe; INVALID_FIELD_OFFSET +method Ljava/nio/ByteBuffer; clear ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/ByteBuffer; flip ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/ByteBuffer; limit (I)Ljava/nio/ByteBuffer; instance +method Ljava/nio/ByteBuffer; mark ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/ByteBuffer; position (I)Ljava/nio/ByteBuffer; instance +method Ljava/nio/ByteBuffer; reset ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/ByteBuffer; rewind ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/CharBuffer; clear ()Ljava/nio/CharBuffer; instance +method Ljava/nio/CharBuffer; flip ()Ljava/nio/CharBuffer; instance +method Ljava/nio/CharBuffer; limit (I)Ljava/nio/CharBuffer; instance +method Ljava/nio/CharBuffer; mark ()Ljava/nio/CharBuffer; instance +method Ljava/nio/CharBuffer; position (I)Ljava/nio/CharBuffer; instance +method Ljava/nio/CharBuffer; reset ()Ljava/nio/CharBuffer; instance +method Ljava/nio/CharBuffer; rewind ()Ljava/nio/CharBuffer; instance +method Ljava/nio/DoubleBuffer; clear ()Ljava/nio/DoubleBuffer; instance +method Ljava/nio/DoubleBuffer; flip ()Ljava/nio/DoubleBuffer; instance +method Ljava/nio/DoubleBuffer; limit (I)Ljava/nio/DoubleBuffer; instance +method Ljava/nio/DoubleBuffer; mark ()Ljava/nio/DoubleBuffer; instance +method Ljava/nio/DoubleBuffer; position (I)Ljava/nio/DoubleBuffer; instance +method Ljava/nio/DoubleBuffer; reset ()Ljava/nio/DoubleBuffer; instance +method Ljava/nio/DoubleBuffer; rewind ()Ljava/nio/DoubleBuffer; instance +method Ljava/nio/FloatBuffer; clear ()Ljava/nio/FloatBuffer; instance +method Ljava/nio/FloatBuffer; flip ()Ljava/nio/FloatBuffer; instance +method Ljava/nio/FloatBuffer; limit (I)Ljava/nio/FloatBuffer; instance +method Ljava/nio/FloatBuffer; mark ()Ljava/nio/FloatBuffer; instance +method Ljava/nio/FloatBuffer; position (I)Ljava/nio/FloatBuffer; instance +method Ljava/nio/FloatBuffer; reset ()Ljava/nio/FloatBuffer; instance +method Ljava/nio/FloatBuffer; rewind ()Ljava/nio/FloatBuffer; instance +method Ljava/nio/IntBuffer; clear ()Ljava/nio/IntBuffer; instance +method Ljava/nio/IntBuffer; flip ()Ljava/nio/IntBuffer; instance +method Ljava/nio/IntBuffer; limit (I)Ljava/nio/IntBuffer; instance +method Ljava/nio/IntBuffer; mark ()Ljava/nio/IntBuffer; instance +method Ljava/nio/IntBuffer; position (I)Ljava/nio/IntBuffer; instance +method Ljava/nio/IntBuffer; reset ()Ljava/nio/IntBuffer; instance +method Ljava/nio/IntBuffer; rewind ()Ljava/nio/IntBuffer; instance +method Ljava/nio/LongBuffer; clear ()Ljava/nio/LongBuffer; instance +method Ljava/nio/LongBuffer; flip ()Ljava/nio/LongBuffer; instance +method Ljava/nio/LongBuffer; limit (I)Ljava/nio/LongBuffer; instance +method Ljava/nio/LongBuffer; mark ()Ljava/nio/LongBuffer; instance +method Ljava/nio/LongBuffer; position (I)Ljava/nio/LongBuffer; instance +method Ljava/nio/LongBuffer; reset ()Ljava/nio/LongBuffer; instance +method Ljava/nio/LongBuffer; rewind ()Ljava/nio/LongBuffer; instance +method Ljava/nio/MappedByteBuffer; clear ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/MappedByteBuffer; clear ()Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/MappedByteBuffer; compact ()Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/MappedByteBuffer; duplicate ()Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/MappedByteBuffer; flip ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/MappedByteBuffer; flip ()Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/MappedByteBuffer; limit (I)Ljava/nio/ByteBuffer; instance +method Ljava/nio/MappedByteBuffer; limit (I)Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/MappedByteBuffer; mark ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/MappedByteBuffer; mark ()Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/MappedByteBuffer; position (I)Ljava/nio/ByteBuffer; instance +method Ljava/nio/MappedByteBuffer; position (I)Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/MappedByteBuffer; reset ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/MappedByteBuffer; reset ()Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/MappedByteBuffer; rewind ()Ljava/nio/ByteBuffer; instance +method Ljava/nio/MappedByteBuffer; rewind ()Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/MappedByteBuffer; slice ()Ljava/nio/MappedByteBuffer; instance +method Ljava/nio/ShortBuffer; clear ()Ljava/nio/ShortBuffer; instance +method Ljava/nio/ShortBuffer; flip ()Ljava/nio/ShortBuffer; instance +method Ljava/nio/ShortBuffer; limit (I)Ljava/nio/ShortBuffer; instance +method Ljava/nio/ShortBuffer; mark ()Ljava/nio/ShortBuffer; instance +method Ljava/nio/ShortBuffer; position (I)Ljava/nio/ShortBuffer; instance +method Ljava/nio/ShortBuffer; reset ()Ljava/nio/ShortBuffer; instance +method Ljava/nio/ShortBuffer; rewind ()Ljava/nio/ShortBuffer; instance +method Ljava/time/LocalDate; getEra ()Ljava/time/chrono/IsoEra; instance +method Ljava/util/concurrent/ConcurrentHashMap; keySet ()Ljava/util/concurrent/ConcurrentHashMap$KeySetView; instance +method Lsun/misc/Unsafe; addressSize ()I instance +method Lsun/misc/Unsafe; allocateInstance (Ljava/lang/Class;)Ljava/lang/Object; instance +method Lsun/misc/Unsafe; allocateMemory (J)J instance +method Lsun/misc/Unsafe; arrayBaseOffset (Ljava/lang/Class;)I instance +method Lsun/misc/Unsafe; arrayIndexScale (Ljava/lang/Class;)I instance +method Lsun/misc/Unsafe; compareAndSwapInt (Ljava/lang/Object;JII)Z instance +method Lsun/misc/Unsafe; compareAndSwapLong (Ljava/lang/Object;JJJ)Z instance +method Lsun/misc/Unsafe; compareAndSwapObject (Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z instance +method Lsun/misc/Unsafe; copyMemory (JJJ)V instance +method Lsun/misc/Unsafe; copyMemoryFromPrimitiveArray (Ljava/lang/Object;JJJ)V instance +method Lsun/misc/Unsafe; copyMemoryToPrimitiveArray (JLjava/lang/Object;JJ)V instance +method Lsun/misc/Unsafe; freeMemory (J)V instance +method Lsun/misc/Unsafe; fullFence ()V instance +method Lsun/misc/Unsafe; getAndAddInt (Ljava/lang/Object;JI)I instance +method Lsun/misc/Unsafe; getAndAddLong (Ljava/lang/Object;JJ)J instance +method Lsun/misc/Unsafe; getAndSetInt (Ljava/lang/Object;JI)I instance +method Lsun/misc/Unsafe; getAndSetLong (Ljava/lang/Object;JJ)J instance +method Lsun/misc/Unsafe; getAndSetObject (Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object; instance +method Lsun/misc/Unsafe; getBoolean (Ljava/lang/Object;J)Z instance +method Lsun/misc/Unsafe; getByte (J)B instance +method Lsun/misc/Unsafe; getByte (Ljava/lang/Object;J)B instance +method Lsun/misc/Unsafe; getChar (J)C instance +method Lsun/misc/Unsafe; getChar (Ljava/lang/Object;J)C instance +method Lsun/misc/Unsafe; getDouble (J)D instance +method Lsun/misc/Unsafe; getDouble (Ljava/lang/Object;J)D instance +method Lsun/misc/Unsafe; getFloat (J)F instance +method Lsun/misc/Unsafe; getFloat (Ljava/lang/Object;J)F instance +method Lsun/misc/Unsafe; getInt (J)I instance +method Lsun/misc/Unsafe; getInt (Ljava/lang/Object;J)I instance +method Lsun/misc/Unsafe; getIntVolatile (Ljava/lang/Object;J)I instance +method Lsun/misc/Unsafe; getLong (J)J instance +method Lsun/misc/Unsafe; getLong (Ljava/lang/Object;J)J instance +method Lsun/misc/Unsafe; getLongVolatile (Ljava/lang/Object;J)J instance +method Lsun/misc/Unsafe; getObject (Ljava/lang/Object;J)Ljava/lang/Object; instance +method Lsun/misc/Unsafe; getObjectVolatile (Ljava/lang/Object;J)Ljava/lang/Object; instance +method Lsun/misc/Unsafe; getShort (J)S instance +method Lsun/misc/Unsafe; getShort (Ljava/lang/Object;J)S instance +method Lsun/misc/Unsafe; getUnsafe ()Lsun/misc/Unsafe; instance +method Lsun/misc/Unsafe; loadFence ()V instance +method Lsun/misc/Unsafe; objectFieldOffset (Ljava/lang/reflect/Field;)J instance +method Lsun/misc/Unsafe; pageSize ()I instance +method Lsun/misc/Unsafe; park (ZJ)V instance +method Lsun/misc/Unsafe; putBoolean (Ljava/lang/Object;JZ)V instance +method Lsun/misc/Unsafe; putByte (JB)V instance +method Lsun/misc/Unsafe; putByte (Ljava/lang/Object;JB)V instance +method Lsun/misc/Unsafe; putChar (JC)V instance +method Lsun/misc/Unsafe; putChar (Ljava/lang/Object;JC)V instance +method Lsun/misc/Unsafe; putDouble (JD)V instance +method Lsun/misc/Unsafe; putDouble (Ljava/lang/Object;JD)V instance +method Lsun/misc/Unsafe; putFloat (JF)V instance +method Lsun/misc/Unsafe; putFloat (Ljava/lang/Object;JF)V instance +method Lsun/misc/Unsafe; putInt (JI)V instance +method Lsun/misc/Unsafe; putInt (Ljava/lang/Object;JI)V instance +method Lsun/misc/Unsafe; putIntVolatile (Ljava/lang/Object;JI)V instance +method Lsun/misc/Unsafe; putLong (JJ)V instance +method Lsun/misc/Unsafe; putLong (Ljava/lang/Object;JJ)V instance +method Lsun/misc/Unsafe; putLongVolatile (Ljava/lang/Object;JJ)V instance +method Lsun/misc/Unsafe; putObject (Ljava/lang/Object;JLjava/lang/Object;)V instance +method Lsun/misc/Unsafe; putObjectVolatile (Ljava/lang/Object;JLjava/lang/Object;)V instance +method Lsun/misc/Unsafe; putOrderedInt (Ljava/lang/Object;JI)V instance +method Lsun/misc/Unsafe; putOrderedLong (Ljava/lang/Object;JJ)V instance +method Lsun/misc/Unsafe; putOrderedObject (Ljava/lang/Object;JLjava/lang/Object;)V instance +method Lsun/misc/Unsafe; putShort (JS)V instance +method Lsun/misc/Unsafe; putShort (Ljava/lang/Object;JS)V instance +method Lsun/misc/Unsafe; setMemory (JJB)V instance +method Lsun/misc/Unsafe; storeFence ()V instance +method Lsun/misc/Unsafe; unpark (Ljava/lang/Object;)V instance
diff --git a/src/main/resources/resources/missing.api.txt b/src/main/resources/resources/missing.api.txt new file mode 100644 index 0000000..5b0ca9d --- /dev/null +++ b/src/main/resources/resources/missing.api.txt
@@ -0,0 +1,177 @@ +// Copyright (c) 2026, 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. +// +// This file is generated by GenerateApiAmendments.java. +// Do not edit manually. +method Landroid/nfc/tech/IsoDep; close ()V 19 +method Landroid/nfc/tech/IsoDep; connect ()V 19 +method Landroid/nfc/tech/IsoDep; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/IsoDep; isConnected ()Z 19 +method Landroid/nfc/tech/MifareClassic; close ()V 19 +method Landroid/nfc/tech/MifareClassic; connect ()V 19 +method Landroid/nfc/tech/MifareClassic; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/MifareClassic; isConnected ()Z 19 +method Landroid/nfc/tech/MifareUltralight; close ()V 19 +method Landroid/nfc/tech/MifareUltralight; connect ()V 19 +method Landroid/nfc/tech/MifareUltralight; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/MifareUltralight; isConnected ()Z 19 +method Landroid/nfc/tech/Ndef; close ()V 19 +method Landroid/nfc/tech/Ndef; connect ()V 19 +method Landroid/nfc/tech/Ndef; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/Ndef; isConnected ()Z 19 +method Landroid/nfc/tech/NdefFormatable; close ()V 19 +method Landroid/nfc/tech/NdefFormatable; connect ()V 19 +method Landroid/nfc/tech/NdefFormatable; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/NdefFormatable; isConnected ()Z 19 +method Landroid/nfc/tech/NfcA; close ()V 19 +method Landroid/nfc/tech/NfcA; connect ()V 19 +method Landroid/nfc/tech/NfcA; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/NfcA; isConnected ()Z 19 +method Landroid/nfc/tech/NfcB; close ()V 19 +method Landroid/nfc/tech/NfcB; connect ()V 19 +method Landroid/nfc/tech/NfcB; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/NfcB; isConnected ()Z 19 +method Landroid/nfc/tech/NfcBarcode; close ()V 19 +method Landroid/nfc/tech/NfcBarcode; connect ()V 19 +method Landroid/nfc/tech/NfcBarcode; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/NfcBarcode; isConnected ()Z 19 +method Landroid/nfc/tech/NfcF; close ()V 19 +method Landroid/nfc/tech/NfcF; connect ()V 19 +method Landroid/nfc/tech/NfcF; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/NfcF; isConnected ()Z 19 +method Landroid/nfc/tech/NfcV; close ()V 19 +method Landroid/nfc/tech/NfcV; connect ()V 19 +method Landroid/nfc/tech/NfcV; getTag ()Landroid/nfc/Tag; 19 +method Landroid/nfc/tech/NfcV; isConnected ()Z 19 +method Landroid/webkit/CookieSyncManager; resetSync ()V 14 +method Landroid/webkit/CookieSyncManager; run ()V 14 +method Landroid/webkit/CookieSyncManager; startSync ()V 14 +method Landroid/webkit/CookieSyncManager; stopSync ()V 14 +method Landroid/webkit/CookieSyncManager; sync ()V 14 +method Ljava/lang/StringBuffer; capacity ()I 1 +method Ljava/lang/StringBuffer; codePointAt (I)I 1 +method Ljava/lang/StringBuffer; codePointBefore (I)I 1 +method Ljava/lang/StringBuffer; codePointCount (II)I 1 +method Ljava/lang/StringBuffer; ensureCapacity (I)V 1 +method Ljava/lang/StringBuffer; getChars (II[CI)V 1 +method Ljava/lang/StringBuffer; indexOf (Ljava/lang/String;)I 1 +method Ljava/lang/StringBuffer; indexOf (Ljava/lang/String;I)I 1 +method Ljava/lang/StringBuffer; lastIndexOf (Ljava/lang/String;)I 1 +method Ljava/lang/StringBuffer; lastIndexOf (Ljava/lang/String;I)I 1 +method Ljava/lang/StringBuffer; offsetByCodePoints (II)I 1 +method Ljava/lang/StringBuffer; setCharAt (IC)V 1 +method Ljava/lang/StringBuffer; setLength (I)V 1 +method Ljava/lang/StringBuffer; substring (I)Ljava/lang/String; 1 +method Ljava/lang/StringBuffer; substring (II)Ljava/lang/String; 1 +method Ljava/lang/StringBuffer; trimToSize ()V 1 +method Ljava/lang/StringBuilder; capacity ()I 1 +method Ljava/lang/StringBuilder; codePointAt (I)I 1 +method Ljava/lang/StringBuilder; codePointBefore (I)I 1 +method Ljava/lang/StringBuilder; codePointCount (II)I 1 +method Ljava/lang/StringBuilder; ensureCapacity (I)V 1 +method Ljava/lang/StringBuilder; getChars (II[CI)V 1 +method Ljava/lang/StringBuilder; indexOf (Ljava/lang/String;)I 1 +method Ljava/lang/StringBuilder; indexOf (Ljava/lang/String;I)I 1 +method Ljava/lang/StringBuilder; lastIndexOf (Ljava/lang/String;)I 1 +method Ljava/lang/StringBuilder; lastIndexOf (Ljava/lang/String;I)I 1 +method Ljava/lang/StringBuilder; offsetByCodePoints (II)I 1 +method Ljava/lang/StringBuilder; setCharAt (IC)V 1 +method Ljava/lang/StringBuilder; setLength (I)V 1 +method Ljava/lang/StringBuilder; substring (I)Ljava/lang/String; 1 +method Ljava/lang/StringBuilder; substring (II)Ljava/lang/String; 1 +method Ljava/lang/StringBuilder; trimToSize ()V 1 +method Ljava/time/chrono/HijrahDate; atTime (Ljava/time/LocalTime;)Ljava/time/chrono/ChronoLocalDateTime; 26 +method Ljava/time/chrono/HijrahDate; getChronology ()Ljava/time/chrono/Chronology; 26 +method Ljava/time/chrono/HijrahDate; getEra ()Ljava/time/chrono/Era; 26 +method Ljava/time/chrono/HijrahDate; getLong (Ljava/time/temporal/TemporalField;)J 26 +method Ljava/time/chrono/HijrahDate; isLeapYear ()Z 26 +method Ljava/time/chrono/HijrahDate; isSupported (Ljava/time/temporal/TemporalField;)Z 26 +method Ljava/time/chrono/HijrahDate; lengthOfMonth ()I 26 +method Ljava/time/chrono/HijrahDate; lengthOfYear ()I 26 +method Ljava/time/chrono/HijrahDate; minus (JLjava/time/temporal/TemporalUnit;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/HijrahDate; minus (JLjava/time/temporal/TemporalUnit;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/HijrahDate; minus (Ljava/time/temporal/TemporalAmount;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/HijrahDate; minus (Ljava/time/temporal/TemporalAmount;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/HijrahDate; plus (JLjava/time/temporal/TemporalUnit;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/HijrahDate; plus (JLjava/time/temporal/TemporalUnit;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/HijrahDate; plus (Ljava/time/temporal/TemporalAmount;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/HijrahDate; plus (Ljava/time/temporal/TemporalAmount;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/HijrahDate; range (Ljava/time/temporal/TemporalField;)Ljava/time/temporal/ValueRange; 26 +method Ljava/time/chrono/HijrahDate; toEpochDay ()J 26 +method Ljava/time/chrono/HijrahDate; until (Ljava/time/chrono/ChronoLocalDate;)Ljava/time/chrono/ChronoPeriod; 26 +method Ljava/time/chrono/HijrahDate; until (Ljava/time/temporal/Temporal;Ljava/time/temporal/TemporalUnit;)J 26 +method Ljava/time/chrono/HijrahDate; with (Ljava/time/temporal/TemporalAdjuster;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/HijrahDate; with (Ljava/time/temporal/TemporalAdjuster;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/HijrahDate; with (Ljava/time/temporal/TemporalField;J)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/HijrahDate; with (Ljava/time/temporal/TemporalField;J)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/JapaneseDate; atTime (Ljava/time/LocalTime;)Ljava/time/chrono/ChronoLocalDateTime; 26 +method Ljava/time/chrono/JapaneseDate; getChronology ()Ljava/time/chrono/Chronology; 26 +method Ljava/time/chrono/JapaneseDate; getEra ()Ljava/time/chrono/Era; 26 +method Ljava/time/chrono/JapaneseDate; getLong (Ljava/time/temporal/TemporalField;)J 26 +method Ljava/time/chrono/JapaneseDate; isSupported (Ljava/time/temporal/TemporalField;)Z 26 +method Ljava/time/chrono/JapaneseDate; lengthOfMonth ()I 26 +method Ljava/time/chrono/JapaneseDate; lengthOfYear ()I 26 +method Ljava/time/chrono/JapaneseDate; minus (JLjava/time/temporal/TemporalUnit;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/JapaneseDate; minus (JLjava/time/temporal/TemporalUnit;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/JapaneseDate; minus (Ljava/time/temporal/TemporalAmount;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/JapaneseDate; minus (Ljava/time/temporal/TemporalAmount;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/JapaneseDate; plus (JLjava/time/temporal/TemporalUnit;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/JapaneseDate; plus (JLjava/time/temporal/TemporalUnit;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/JapaneseDate; plus (Ljava/time/temporal/TemporalAmount;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/JapaneseDate; plus (Ljava/time/temporal/TemporalAmount;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/JapaneseDate; range (Ljava/time/temporal/TemporalField;)Ljava/time/temporal/ValueRange; 26 +method Ljava/time/chrono/JapaneseDate; toEpochDay ()J 26 +method Ljava/time/chrono/JapaneseDate; until (Ljava/time/chrono/ChronoLocalDate;)Ljava/time/chrono/ChronoPeriod; 26 +method Ljava/time/chrono/JapaneseDate; until (Ljava/time/temporal/Temporal;Ljava/time/temporal/TemporalUnit;)J 26 +method Ljava/time/chrono/JapaneseDate; with (Ljava/time/temporal/TemporalAdjuster;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/JapaneseDate; with (Ljava/time/temporal/TemporalAdjuster;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/JapaneseDate; with (Ljava/time/temporal/TemporalField;J)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/JapaneseDate; with (Ljava/time/temporal/TemporalField;J)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/MinguoDate; atTime (Ljava/time/LocalTime;)Ljava/time/chrono/ChronoLocalDateTime; 26 +method Ljava/time/chrono/MinguoDate; getChronology ()Ljava/time/chrono/Chronology; 26 +method Ljava/time/chrono/MinguoDate; getEra ()Ljava/time/chrono/Era; 26 +method Ljava/time/chrono/MinguoDate; getLong (Ljava/time/temporal/TemporalField;)J 26 +method Ljava/time/chrono/MinguoDate; isSupported (Ljava/time/temporal/TemporalField;)Z 26 +method Ljava/time/chrono/MinguoDate; lengthOfMonth ()I 26 +method Ljava/time/chrono/MinguoDate; lengthOfYear ()I 26 +method Ljava/time/chrono/MinguoDate; minus (JLjava/time/temporal/TemporalUnit;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/MinguoDate; minus (JLjava/time/temporal/TemporalUnit;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/MinguoDate; minus (Ljava/time/temporal/TemporalAmount;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/MinguoDate; minus (Ljava/time/temporal/TemporalAmount;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/MinguoDate; plus (JLjava/time/temporal/TemporalUnit;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/MinguoDate; plus (JLjava/time/temporal/TemporalUnit;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/MinguoDate; plus (Ljava/time/temporal/TemporalAmount;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/MinguoDate; plus (Ljava/time/temporal/TemporalAmount;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/MinguoDate; range (Ljava/time/temporal/TemporalField;)Ljava/time/temporal/ValueRange; 26 +method Ljava/time/chrono/MinguoDate; toEpochDay ()J 26 +method Ljava/time/chrono/MinguoDate; until (Ljava/time/chrono/ChronoLocalDate;)Ljava/time/chrono/ChronoPeriod; 26 +method Ljava/time/chrono/MinguoDate; until (Ljava/time/temporal/Temporal;Ljava/time/temporal/TemporalUnit;)J 26 +method Ljava/time/chrono/MinguoDate; with (Ljava/time/temporal/TemporalAdjuster;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/MinguoDate; with (Ljava/time/temporal/TemporalAdjuster;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/MinguoDate; with (Ljava/time/temporal/TemporalField;J)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/MinguoDate; with (Ljava/time/temporal/TemporalField;J)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/ThaiBuddhistDate; atTime (Ljava/time/LocalTime;)Ljava/time/chrono/ChronoLocalDateTime; 26 +method Ljava/time/chrono/ThaiBuddhistDate; getChronology ()Ljava/time/chrono/Chronology; 26 +method Ljava/time/chrono/ThaiBuddhistDate; getEra ()Ljava/time/chrono/Era; 26 +method Ljava/time/chrono/ThaiBuddhistDate; getLong (Ljava/time/temporal/TemporalField;)J 26 +method Ljava/time/chrono/ThaiBuddhistDate; isSupported (Ljava/time/temporal/TemporalField;)Z 26 +method Ljava/time/chrono/ThaiBuddhistDate; lengthOfMonth ()I 26 +method Ljava/time/chrono/ThaiBuddhistDate; lengthOfYear ()I 26 +method Ljava/time/chrono/ThaiBuddhistDate; minus (JLjava/time/temporal/TemporalUnit;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/ThaiBuddhistDate; minus (JLjava/time/temporal/TemporalUnit;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/ThaiBuddhistDate; minus (Ljava/time/temporal/TemporalAmount;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/ThaiBuddhistDate; minus (Ljava/time/temporal/TemporalAmount;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/ThaiBuddhistDate; plus (JLjava/time/temporal/TemporalUnit;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/ThaiBuddhistDate; plus (JLjava/time/temporal/TemporalUnit;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/ThaiBuddhistDate; plus (Ljava/time/temporal/TemporalAmount;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/ThaiBuddhistDate; plus (Ljava/time/temporal/TemporalAmount;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/ThaiBuddhistDate; range (Ljava/time/temporal/TemporalField;)Ljava/time/temporal/ValueRange; 26 +method Ljava/time/chrono/ThaiBuddhistDate; toEpochDay ()J 26 +method Ljava/time/chrono/ThaiBuddhistDate; until (Ljava/time/chrono/ChronoLocalDate;)Ljava/time/chrono/ChronoPeriod; 26 +method Ljava/time/chrono/ThaiBuddhistDate; until (Ljava/time/temporal/Temporal;Ljava/time/temporal/TemporalUnit;)J 26 +method Ljava/time/chrono/ThaiBuddhistDate; with (Ljava/time/temporal/TemporalAdjuster;)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/ThaiBuddhistDate; with (Ljava/time/temporal/TemporalAdjuster;)Ljava/time/temporal/Temporal; 26 +method Ljava/time/chrono/ThaiBuddhistDate; with (Ljava/time/temporal/TemporalField;J)Ljava/time/chrono/ChronoLocalDate; 26 +method Ljava/time/chrono/ThaiBuddhistDate; with (Ljava/time/temporal/TemporalField;J)Ljava/time/temporal/Temporal; 26 +method Ljava/util/concurrent/ConcurrentHashMap$KeySetView; getMap ()Ljava/util/concurrent/ConcurrentHashMap; 24
diff --git a/src/test/bootstrap/com/android/tools/r8/bootstrap/SanityCheck.java b/src/test/bootstrap/com/android/tools/r8/bootstrap/SanityCheck.java index 5a23fbb..418857b 100644 --- a/src/test/bootstrap/com/android/tools/r8/bootstrap/SanityCheck.java +++ b/src/test/bootstrap/com/android/tools/r8/bootstrap/SanityCheck.java
@@ -66,7 +66,10 @@ throws Exception { BooleanBox licenseSeen = new BooleanBox(); BooleanBox manifestSeen = new BooleanBox(); - Set<String> apiDatabaseFiles = Sets.newHashSet("resources/api_database.ser"); + BooleanBox apiDatabaseSeen = new BooleanBox(); + Set<String> apiResourceFiles = + Sets.newHashSet( + "resources/missing.api.txt", "resources/hidden.api.txt", "resources/hidden.jar.txt"); Set<String> r8AssistantRuntime = ImmutableSet.of( "ReflectiveEventType.java", @@ -128,18 +131,21 @@ // Allow. } else if (entryTester.test(name)) { // Allow. - } else if (apiDatabaseFiles.contains(name)) { - // Allow all api database files. - apiDatabaseFiles.remove(name); + } else if (name.equals("resources/api_database.ser")) { + apiDatabaseSeen.set(); // Check that the entry is stored uncompressed. - assertEquals(ZipEntry.STORED, entry.getMethod()); + assertEquals(name + " has the wrong zip-method", ZipEntry.STORED, entry.getMethod()); + } else if (apiResourceFiles.contains(name)) { + // Allow all api database resource files. + apiResourceFiles.remove(name); } else if (name.endsWith("/")) { assertTrue("Unexpected directory entry in" + jar, allowDirectories); } else { fail("Unexpected entry '" + name + "' in " + jar); } }); - assertTrue(apiDatabaseFiles.isEmpty()); + assertTrue(apiResourceFiles.isEmpty()); + assertTrue("No resources/api_database.ser entry found in " + jar, apiDatabaseSeen.isTrue()); assertTrue("No LICENSE entry found in " + jar, licenseSeen.isTrue()); assertTrue("No META-INF/MANIFEST.MF entry found in " + jar, manifestSeen.isTrue()); } catch (IOException e) {
diff --git a/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParserTest.java b/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParserTest.java index a955b75..05482ef 100644 --- a/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParserTest.java +++ b/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorCommandParserTest.java
@@ -5,7 +5,11 @@ import static org.junit.Assert.assertEquals; +import com.android.tools.r8.origin.CommandLineOrigin; import com.android.tools.r8.utils.internal.StringUtils; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -27,19 +31,27 @@ public void testHelpMessage() { assertEquals( StringUtils.lines( - "Usage: apidatabasegenerator [options] <input-files>", - "where <input-files> are Android API XML files (e.g., api-versions.xml) to merge,", - "OR Android SDK Jar files (e.g., android.jar).", - "The XML lack sufficient information so the Jar files are used to add information to" - + " entries.", + "Usage: apidatabasegenerator [options]", + "Combines Android API information (--xml) with Android SDK information (--jar)", + "into an API database file required for compilation.", + "Multiple inputs of both are supported and any entries not present in both JAR and XML", + "form is trimmed away.", "The options are:", " --help", " -h # Print help.", " --version # Print version.", + " --jar <jar-file> # Android SDK JAR file (e.g., android.jar).", + " --xml <xml-file> # Android API XML file (e.g., api-versions.xml).", + " --sdk <sdk-dir> # Android SDK platform directory (e.g.," + + " $ANDROID_HOME/android-34).", " --output <database-file>", " # Output result in <database-file> (must be a file, not a" + " directory).", " # Defaults to 'api_database.ser'.", + " --dont-amend # By default, the API database is amended with known missing" + + " information.", + " # This option disables that and processes inputs directly as" + + " they are.", " --map-diagnostics[:<type>] <from-level> <to-level>", " # Map diagnostics of <type> (default any) reported as" + " <from-level> to", @@ -54,4 +66,82 @@ " # errors cannot be mapped."), ApiDatabaseGeneratorCommandParser.getUsageMessage()); } + + @Test + public void testParseJarAndXml() throws Exception { + Path jarFile = temp.newFile("android.jar").toPath(); + Path xmlFile = temp.newFile("api-versions.xml").toPath(); + String[] args = {"--jar", jarFile.toString(), "--xml", xmlFile.toString()}; + ApiDatabaseGeneratorCommand command = + ApiDatabaseGeneratorCommand.parse(args, CommandLineOrigin.INSTANCE).build(); + assertEquals(Collections.singletonList(jarFile), command.getJarPaths()); + assertEquals(Collections.singletonList(xmlFile), command.getXmlPaths()); + } + + @Test + public void testParseMultipleJarsAndXmls() throws Exception { + Path jar1 = temp.newFile("android1.jar").toPath(); + Path jar2 = temp.newFile("android2.jar").toPath(); + Path xml1 = temp.newFile("api1.xml").toPath(); + Path xml2 = temp.newFile("api2.xml").toPath(); + String[] args = { + "--jar", jar1.toString(), + "--jar", jar2.toString(), + "--xml", xml1.toString(), + "--xml", xml2.toString() + }; + ApiDatabaseGeneratorCommand command = + ApiDatabaseGeneratorCommand.parse(args, CommandLineOrigin.INSTANCE).build(); + assertEquals(Arrays.asList(jar1, jar2), command.getJarPaths()); + assertEquals(Arrays.asList(xml1, xml2), command.getXmlPaths()); + } + + @Test + public void testParseSdkDirectory() throws Exception { + Path sdkDir = temp.newFolder("sdk").toPath(); + String[] args = {"--sdk", sdkDir.toString()}; + ApiDatabaseGeneratorCommand command = + ApiDatabaseGeneratorCommand.parse(args, CommandLineOrigin.INSTANCE).build(); + assertEquals(Collections.singletonList(sdkDir.resolve("android.jar")), command.getJarPaths()); + assertEquals( + Collections.singletonList(sdkDir.resolve("data").resolve("api-versions.xml")), + command.getXmlPaths()); + } + + @Test + public void testParseMultipleSdks() throws Exception { + Path sdkDir1 = temp.newFolder("sdk1").toPath(); + Path sdkDir2 = temp.newFolder("sdk2").toPath(); + + String[] args = {"--sdk", sdkDir1.toString(), "--sdk", sdkDir2.toString()}; + ApiDatabaseGeneratorCommand command = + ApiDatabaseGeneratorCommand.parse(args, CommandLineOrigin.INSTANCE).build(); + assertEquals( + Arrays.asList(sdkDir1.resolve("android.jar"), sdkDir2.resolve("android.jar")), + command.getJarPaths()); + assertEquals( + Arrays.asList( + sdkDir1.resolve("data").resolve("api-versions.xml"), + sdkDir2.resolve("data").resolve("api-versions.xml")), + command.getXmlPaths()); + } + + @Test + public void testParseSdkCombinedWithJarAndXml() throws Exception { + Path sdkDir = temp.newFolder("sdk").toPath(); + Path extraJar = temp.newFile("extra.jar").toPath(); + Path extraXml = temp.newFile("extra.xml").toPath(); + + String[] args = { + "--sdk", sdkDir.toString(), + "--jar", extraJar.toString(), + "--xml", extraXml.toString() + }; + ApiDatabaseGeneratorCommand command = + ApiDatabaseGeneratorCommand.parse(args, CommandLineOrigin.INSTANCE).build(); + assertEquals(Arrays.asList(sdkDir.resolve("android.jar"), extraJar), command.getJarPaths()); + assertEquals( + Arrays.asList(sdkDir.resolve("data").resolve("api-versions.xml"), extraXml), + command.getXmlPaths()); + } }
diff --git a/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorTest.java b/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorTest.java index 71abf3b..23eb880 100644 --- a/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorTest.java +++ b/src/test/java/com/android/tools/r8/ApiDatabaseGeneratorTest.java
@@ -58,9 +58,10 @@ ApiDatabaseGeneratorCommand command = ApiDatabaseGeneratorCommand.builder() - .addInputPath(apiVersionsXml) - .addInputPath(dummyJar) + .addXmlPath(apiVersionsXml) + .addJarPath(dummyJar) .setOutputPath(outputDb) + .setAmend(false) .build(); ApiDatabaseGenerator.run(command); @@ -99,10 +100,11 @@ TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); ApiDatabaseGeneratorCommand command = ApiDatabaseGeneratorCommand.builder(diagnosticsHandler) - .addInputPath(apiVersionsXml1) - .addInputPath(apiVersionsXml2) - .addInputPath(dummyJar) + .addXmlPath(apiVersionsXml1) + .addXmlPath(apiVersionsXml2) + .addJarPath(dummyJar) .setOutputPath(outputDb) + .setAmend(false) .build(); try { @@ -153,11 +155,12 @@ TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); ApiDatabaseGeneratorCommand command = ApiDatabaseGeneratorCommand.builder(diagnosticsHandler) - .addInputPath(apiVersionsXml1) - .addInputPath(apiVersionsXml2) - .addInputPath(apiVersionsXml3) - .addInputPath(dummyJar) + .addXmlPath(apiVersionsXml1) + .addXmlPath(apiVersionsXml2) + .addXmlPath(apiVersionsXml3) + .addJarPath(dummyJar) .setOutputPath(outputDb) + .setAmend(false) .build(); try { @@ -203,11 +206,15 @@ String[] args = { "--output", outputDb.toString(), + "--dont-amend", "--map-diagnostics:DuplicateApiDatabaseEntryDiagnostic", "error", "info", + "--xml", apiVersionsXml1.toString(), + "--xml", apiVersionsXml2.toString(), + "--jar", dummyJar.toString() }; @@ -259,11 +266,15 @@ String[] args = { "--output", outputDb.toString(), + "--dont-amend", "--map-diagnostics:DuplicateApiDatabaseEntryDiagnostic", "error", "none", + "--xml", apiVersionsXml1.toString(), + "--xml", apiVersionsXml2.toString(), + "--jar", dummyJar.toString() }; @@ -286,7 +297,7 @@ Path jarFile = temp.newFile("android.jar").toPath(); ApiDatabaseGeneratorCommand command = - ApiDatabaseGeneratorCommand.builder().addInputPath(xmlFile).addInputPath(jarFile).build(); + ApiDatabaseGeneratorCommand.builder().addXmlPath(xmlFile).addJarPath(jarFile).build(); assertEquals(1, command.getXmlPaths().size()); assertEquals(xmlFile, command.getXmlPaths().get(0)); @@ -295,21 +306,68 @@ } @Test - public void testInvalidInputExtension() throws Exception { - Path txtFile = temp.newFile("invalid.txt").toPath(); + public void testSdkInput() throws Exception { + Path sdkDir = temp.newFolder("sdk").toPath(); + + ApiDatabaseGeneratorCommand command = + ApiDatabaseGeneratorCommand.builder().addSdkPath(sdkDir).build(); + + assertEquals(1, command.getXmlPaths().size()); + assertEquals(sdkDir.resolve("data").resolve("api-versions.xml"), command.getXmlPaths().get(0)); + assertEquals(1, command.getJarPaths().size()); + assertEquals(sdkDir.resolve("android.jar"), command.getJarPaths().get(0)); + } + + @Test + public void testMissingJarInput() throws Exception { + Path xmlFile = temp.newFile("api-versions.xml").toPath(); TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); try { - ApiDatabaseGeneratorCommand.builder(diagnosticsHandler).addInputPath(txtFile).build(); - fail("Expected Command to fail building due to invalid input extension"); + ApiDatabaseGeneratorCommand.builder(diagnosticsHandler).addXmlPath(xmlFile).build(); + fail("Expected Command to fail building due to missing JAR input"); } catch (ApiDatabaseGeneratorException e) { // Expected. } diagnosticsHandler.assertErrorsMatch( - diagnosticMessage(containsString("Unsupported input file extension")), - diagnosticMessage(containsString("At least one SDK JAR")), - diagnosticMessage(containsString("At least one API XML"))); + diagnosticMessage(containsString("At least one SDK JAR input path must be specified"))); + } + + @Test + public void testMissingXmlInput() throws Exception { + Path jarFile = temp.newFile("android.jar").toPath(); + + TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); + try { + ApiDatabaseGeneratorCommand.builder(diagnosticsHandler).addJarPath(jarFile).build(); + fail("Expected Command to fail building due to missing XML input"); + } catch (ApiDatabaseGeneratorException e) { + // Expected. + } + + diagnosticsHandler.assertErrorsMatch( + diagnosticMessage(containsString("At least one API XML input path must be specified"))); + } + + @Test + public void testUnexpectedPositionalArgument() throws Exception { + Path jarFile = temp.newFile("android.jar").toPath(); + Path xmlFile = temp.newFile("api-versions.xml").toPath(); + Path txtFile = temp.newFile("invalid.txt").toPath(); + + TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); + String[] args = {"--jar", jarFile.toString(), "--xml", xmlFile.toString(), txtFile.toString()}; + try { + ApiDatabaseGeneratorCommand.parse(args, CommandLineOrigin.INSTANCE, diagnosticsHandler) + .build(); + fail("Expected Command to fail building due to unexpected positional argument"); + } catch (ApiDatabaseGeneratorException e) { + // Expected. + } + + diagnosticsHandler.assertErrorsMatch( + diagnosticMessage(containsString("Unexpected argument: " + txtFile))); } private Path writeApiXml(String filename, String... contentLines) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java index 7996e83..11e7dda 100644 --- a/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java +++ b/src/test/java/com/android/tools/r8/dex/DebugByteCodeWriterTest.java
@@ -5,7 +5,6 @@ import static com.android.tools.r8.TestBase.getTestParameters; -import com.android.tools.r8.DexIndexedConsumer; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.graph.AppInfo; @@ -44,13 +43,12 @@ parameters.assertNoneRuntime(); } - private static ObjectToOffsetMapping emptyObjectTObjectMapping() { - InternalOptions options = new InternalOptions(new DexItemFactory(), new Reporter()); - options.programConsumer = DexIndexedConsumer.emptyConsumer(); + private ObjectToOffsetMapping emptyObjectTObjectMapping() { AppView<AppInfo> appView = AppView.createForD8( AppInfo.createInitialAppInfo( - DexApplication.builder(options).build(Timing.empty()), + DexApplication.builder(new InternalOptions(new DexItemFactory(), new Reporter())) + .build(Timing.empty()), GlobalSyntheticsStrategy.forNonSynthesizing())); return new ObjectToOffsetMapping( appView,
diff --git a/src/test/java/com/android/tools/r8/dex/MaxTypeLimitTest.java b/src/test/java/com/android/tools/r8/dex/MaxTypeLimitTest.java index d8e9e67..63825d5 100644 --- a/src/test/java/com/android/tools/r8/dex/MaxTypeLimitTest.java +++ b/src/test/java/com/android/tools/r8/dex/MaxTypeLimitTest.java
@@ -3,16 +3,22 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.dex; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import com.android.tools.r8.ByteDataView; import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer; +import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.dex.container.DexContainerFormatTestBase; -import com.android.tools.r8.transformers.ClassTransformer; +import com.android.tools.r8.errors.DexFileOverflowDiagnostic; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.ZipUtils; @@ -26,13 +32,18 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; +import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; @RunWith(Parameterized.class) public class MaxTypeLimitTest extends TestBase { - private static final int CLASS_COUNT = 33000; - private static Path inputApp; + private static final int CLASS_COUNT_32K = 33000; + private static final int CLASS_COUNT_64K = 65533; + private static final int TOTAL_TYPES_64K = 65536; + private static Path inputApp32K; + private static Path inputApp64K; @Parameter(0) public TestParameters parameters; @@ -44,35 +55,41 @@ @BeforeClass public static void generateTestApplication() { - inputApp = getStaticTemp().getRoot().toPath().resolve("input_app.jar"); - generateApplication(inputApp, CLASS_COUNT); + inputApp32K = getStaticTemp().getRoot().toPath().resolve("input_app_32k.jar"); + generateApplication(inputApp32K, CLASS_COUNT_32K, false); + inputApp64K = getStaticTemp().getRoot().toPath().resolve("input_app_64k.jar"); + generateApplication(inputApp64K, CLASS_COUNT_64K, true); } - private static void generateApplication(Path output, int classCount) { - List<String> classes = new ArrayList<>(classCount); - for (int i = 0; i < classCount; ++i) { - classes.add("package_" + (i % 2 == 0 ? "a" : "b") + ".Class" + i); - } + private static void generateApplication(Path output, int classCount, boolean includeMain) { ArchiveConsumer consumer = new ArchiveConsumer(output); - for (String typename : classes) { + for (int i = 0; i < classCount; ++i) { + String typename = "package_" + (i % 2 == 0 ? "a" : "b") + ".Class" + i; String descriptor = DescriptorUtils.javaTypeToDescriptor(typename); - byte[] bytes = - transformer(ClassStub.class) - .setClassDescriptor(descriptor) - .addClassTransformer( - new ClassTransformer() { - @Override - public MethodVisitor visitMethod( - int access, - String name, - String descriptor, - String signature, - String[] exceptions) { - // Strip methods to only generate class definition without methods. - return null; - } - }) - .transform(); + String internalName = descriptor.substring(1, descriptor.length() - 1); + ClassWriter cw = new ClassWriter(0); + cw.visit( + Opcodes.V1_8, + Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, + internalName, + null, + "java/lang/Object", + null); + if (includeMain && i == 0) { + MethodVisitor mv = + cw.visitMethod( + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "main", + "([Ljava/lang/String;)V", + null, + null); + mv.visitCode(); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(0, 1); + mv.visitEnd(); + } + cw.visitEnd(); + byte[] bytes = cw.toByteArray(); consumer.accept(ByteDataView.of(bytes), descriptor, null); } consumer.finished(null); @@ -86,10 +103,10 @@ @Test public void testD8Api25MultidexSplitOnTypes() throws Exception { - // API level 25 only allows 32767 type-ids, so 33000 classes must split into 2 DEX files. + // API level 25 only allows 32768 type-ids, so 33000 classes must split into 2 DEX files. Path output = testForD8(Backend.DEX) - .addProgramFiles(inputApp) + .addProgramFiles(inputApp32K) .setMinApi(AndroidApiLevel.N_MR1) .compile() .writeToZip(); @@ -106,10 +123,10 @@ @Test public void testD8Api26SingleDex() throws Exception { - // API level 26 allows 65536 type-ids, so 33000 classes fit into 1 DEX file. + // API level 26 allows 65535 type-ids, so 33000 classes fit into 1 DEX file. Path output = testForD8(Backend.DEX) - .addProgramFiles(inputApp) + .addProgramFiles(inputApp32K) .setMinApi(AndroidApiLevel.O) .compile() .writeToZip(); @@ -120,49 +137,119 @@ assertTrue( "Type count " + typeCount + " must be > 32768", typeCount > VirtualFile.MAX_ENTRIES_ONLY_32K); + assertTrue( + "Type count " + typeCount + " must be <= 65535", + typeCount <= VirtualFile.MAX_ENTRIES_ONLY_65535); } @Test public void testD8Api25SingleDexOverflow() throws Exception { - // The workaround is not applied when compiling to mono-dex (e.g. min-api < 21), so compilation - // should succeed even with 33000 classes. - Path output = - testForD8(Backend.DEX) - .addProgramFiles(inputApp) - .setMinApi(AndroidApiLevel.K) - .compile() - .writeToZip(); - List<byte[]> dexes = new ArrayList<>(); - ZipUtils.iter(output, (entry, inputStream) -> dexes.add(ByteStreams.toByteArray(inputStream))); - assertEquals(1, dexes.size()); - int typeCount = getTypeCount(dexes.get(0)); - assertTrue( - "Type count " + typeCount + " must be > 32768", - typeCount > VirtualFile.MAX_ENTRIES_ONLY_32K); + // Compiling with mono-dex (e.g. min-api < 21) with 33000 classes should fail. + assertThrows( + CompilationFailedException.class, + () -> + testForD8(Backend.DEX) + .addProgramFiles(inputApp32K) + .setMinApi(AndroidApiLevel.K) + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics.assertErrorsMatch( + allOf( + diagnosticType(DexFileOverflowDiagnostic.class), + diagnosticMessage( + containsString( + "# types: " + (CLASS_COUNT_32K + 1) + " > 32768")))))); + // Should not fail when alwaysAllow64KTypeIds is set. + testForD8(Backend.DEX) + .addProgramFiles(inputApp32K) + .setMinApi(AndroidApiLevel.K) + .addOptionsModification(options -> options.alwaysAllow64KTypeIds = true) + .compile(); } @Test public void testD8LegacyMultidexMainDexRulesOverflow() throws Exception { - // The workaround is not applied when compiling to legacy multi dex, so compilation should - // succeed even with 33000 classes in the presence of main dex rules. + // Compiling with main-dex rules that keep all 33000 classes in main dex on legacy multidex (API + // 19) should fail. + assertThrows( + CompilationFailedException.class, + () -> + testForD8(Backend.DEX) + .addProgramFiles(inputApp32K) + .setMinApi(AndroidApiLevel.K) + .addMainDexRules("-keep class ** { *; }") + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics.assertErrorsMatch( + allOf( + diagnosticType(DexFileOverflowDiagnostic.class), + diagnosticMessage( + containsString( + "Cannot fit requested classes in the main-dex file (#" + + " types: " + + (CLASS_COUNT_32K + 1) + + " > 32768)")))))); + + // Should not fail when alwaysAllow64KTypeIds is set. + testForD8(Backend.DEX) + .addProgramFiles(inputApp32K) + .setMinApi(AndroidApiLevel.K) + .addMainDexRules("-keep class ** { *; }") + .addOptionsModification(options -> options.alwaysAllow64KTypeIds = true) + .compile(); + } + + @Test + public void testD8Api26MultidexSplitOn65KTypes() throws Exception { + // API level 26 allows at most 65535 type-ids, so 65536 types must split into 2 DEX files. Path output = testForD8(Backend.DEX) - .addProgramFiles(inputApp) - .setMinApi(AndroidApiLevel.K) - .addMainDexRules("-keep class ** { *; }") + .addProgramFiles(inputApp64K) + .setMinApi(AndroidApiLevel.O) .compile() .writeToZip(); List<byte[]> dexes = new ArrayList<>(); ZipUtils.iter(output, (entry, inputStream) -> dexes.add(ByteStreams.toByteArray(inputStream))); - assertEquals(1, dexes.size()); - int typeCount = getTypeCount(dexes.get(0)); - assertTrue( - "Type count " + typeCount + " must be > 32768", - typeCount > VirtualFile.MAX_ENTRIES_ONLY_32K); + assertEquals(2, dexes.size()); + for (byte[] dex : dexes) { + int typeCount = getTypeCount(dex); + assertTrue( + "Type count " + typeCount + " must be <= 65535", + typeCount <= VirtualFile.MAX_ENTRIES_ONLY_65535); + } } - // Simple stub/template for generating the input classes. - public static class ClassStub { - public static void methodStub() {} + @Test + public void testD8Api26SingleDexOverflow() throws Exception { + // Compiling with mono-dex with 65536 types on API 19 should fail because 65536 > 32768. + assertThrows( + CompilationFailedException.class, + () -> + testForD8(Backend.DEX) + .addProgramFiles(inputApp64K) + .setMinApi(AndroidApiLevel.K) + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics.assertErrorsMatch( + allOf( + diagnosticType(DexFileOverflowDiagnostic.class), + diagnosticMessage( + containsString("# types: " + TOTAL_TYPES_64K + " > 32768")))))); + + // Compiling with alwaysAllow64KTypeIds allows at most 65535 types, so 65536 types still fails. + assertThrows( + CompilationFailedException.class, + () -> + testForD8(Backend.DEX) + .addProgramFiles(inputApp64K) + .setMinApi(AndroidApiLevel.K) + .addOptionsModification(options -> options.alwaysAllow64KTypeIds = true) + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics.assertErrorsMatch( + allOf( + diagnosticType(DexFileOverflowDiagnostic.class), + diagnosticMessage( + containsString("# types: " + TOTAL_TYPES_64K + " > 65535")))))); } }
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java b/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java index 8790618..32fb392 100644 --- a/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java +++ b/src/test/java/com/android/tools/r8/jasmin/Regress65007724.java
@@ -3,12 +3,19 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.jasmin; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import com.android.tools.r8.SingleTestRunResult; +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.D8TestBuilder; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.errors.DexFileOverflowDiagnostic; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.ZipUtils; import java.io.IOException; @@ -50,24 +57,42 @@ "ldc \"Hello World!\"", "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V", "return"); + D8TestBuilder testBuilder = + testForD8(parameters.getBackend()) + .setMinApi(parameters) + .addProgramClassFileData(builder.buildClasses()); - SingleTestRunResult<?> d8TestRunResult = - testForD8(parameters) - .addProgramClassFileData(builder.buildClasses()) - .compile() - .writeToZip( - p -> { - try { - assertTrue(ZipUtils.containsEntry(p, "classes.dex")); - assertEquals( - parameters.canUseNativeMultidex() - && parameters.getApiLevel().isLessThan(AndroidApiLevel.O), - ZipUtils.containsEntry(p, "classes2.dex")); - } catch (IOException e) { - throw new RuntimeException(e); - } - }) - .run(parameters.getRuntime(), clazz.name); - d8TestRunResult.assertSuccessWithOutput("Hello World!"); + if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L)) { + testBuilder + .compile() + .writeToZip( + p -> { + try { + assertTrue(ZipUtils.containsEntry(p, "classes.dex")); + assertEquals( + parameters.getApiLevel().isLessThan(AndroidApiLevel.O), + ZipUtils.containsEntry(p, "classes2.dex")); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .run(parameters.getRuntime(), clazz.name) + .assertSuccessWithOutput("Hello World!"); + } else { + assertThrows( + CompilationFailedException.class, + () -> + testBuilder.compileWithExpectedDiagnostics( + diagnostics -> + diagnostics + .assertOnlyErrors() + .assertAllErrorsMatch( + allOf( + diagnosticType(DexFileOverflowDiagnostic.class), + diagnosticMessage( + containsString( + "Cannot fit requested classes in a single dex file (#" + + " types: 35007 > 32768)")))))); + } } }
diff --git a/src/test/java/com/android/tools/r8/mappingcompose/ComposeMergedClassesTest.java b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMergedClassesTest.java new file mode 100644 index 0000000..709391f --- /dev/null +++ b/src/test/java/com/android/tools/r8/mappingcompose/ComposeMergedClassesTest.java
@@ -0,0 +1,56 @@ +// Copyright (c) 2026, 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.mappingcompose; + +import static com.android.tools.r8.mappingcompose.ComposeTestHelpers.doubleToSingleQuote; +import static org.junit.Assert.assertEquals; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.naming.ClassNameMapper; +import com.android.tools.r8.naming.MappingComposer; +import com.android.tools.r8.utils.internal.StringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ComposeMergedClassesTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + private static final String mappingFoo = + StringUtils.unixLines( + "# { id: 'com.android.tools.r8.mapping', version: '2.3' }", + "com.foo -> a:", + "# { id: 'com.android.tools.r8.mergedClasses', kind: 'horizontal', classIdField: '$cid'," + + " mergedClasses: [{ name: 'com.foo', classId: '0' }, { name: 'com.bar', classId:" + + " '1' }] }"); + private static final String mappingBar = + StringUtils.unixLines("# { id: 'com.android.tools.r8.mapping', version: '2.2' }", "a -> b:"); + private static final String mappingResult = + StringUtils.unixLines( + "# {'id':'com.android.tools.r8.mapping','version':'2.3'}", + "com.foo -> b:", + "# {'id':'com.android.tools.r8.mergedClasses','kind':'horizontal','classIdField':'$cid'," + + "'mergedClasses':[{'name':'com.foo','classId':'0'},{'name':'com.bar'," + + "'classId':'1'}]}"); + + @Test + public void testCompose() throws Exception { + ClassNameMapper mappingForFoo = ClassNameMapper.mapperFromStringWithPreamble(mappingFoo); + ClassNameMapper mappingForBar = ClassNameMapper.mapperFromStringWithPreamble(mappingBar); + String composed = MappingComposer.compose(mappingForFoo, mappingForBar); + assertEquals(mappingResult, doubleToSingleQuote(composed)); + } +}
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/CannotUnboxNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/CannotUnboxNumberUnboxingTest.java index c4acb9d..c96bd86 100644 --- a/src/test/java/com/android/tools/r8/numberunboxing/CannotUnboxNumberUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/numberunboxing/CannotUnboxNumberUnboxingTest.java
@@ -36,12 +36,11 @@ @Test public void testNumberUnboxing() throws Exception { - testForR8(parameters.getBackend()) + testForR8(parameters) .addInnerClasses(getClass()) .addKeepMainRule(Main.class) .enableInliningAnnotations() .addOptionsModification(opt -> opt.getTestingOptions().getNumberUnboxerOptions().enable()) - .setMinApi(parameters) .compile() .inspect(this::assertUnboxing) .run(parameters.getRuntime(), Main.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 index 1d16a94..ee4bf47 100644 --- a/src/test/java/com/android/tools/r8/numberunboxing/InitializerNumberUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/numberunboxing/InitializerNumberUnboxingTest.java
@@ -38,12 +38,11 @@ @Test public void testNumberUnboxing() throws Exception { - testForR8(parameters.getBackend()) + testForR8(parameters) .addInnerClasses(getClass()) .addKeepMainRule(Main.class) .enableInliningAnnotations() .addOptionsModification(opt -> opt.getTestingOptions().getNumberUnboxerOptions().enable()) - .setMinApi(parameters) .compile() .inspect(this::assertUnboxing) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsCollidingNamesNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsCollidingNamesNumberUnboxingTest.java new file mode 100644 index 0000000..b3cde74 --- /dev/null +++ b/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsCollidingNamesNumberUnboxingTest.java
@@ -0,0 +1,105 @@ +// Copyright (c) 2026, 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 junit.framework.TestCase.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoParameterTypeStrengthening; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class StaticMethodsCollidingNamesNumberUnboxingTest extends TestBase { + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public StaticMethodsCollidingNamesNumberUnboxingTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testNumberUnboxing() throws Exception { + testForR8(parameters) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .addNoParameterTypeStrengtheningAnnotation() + .addOptionsModification(opt -> opt.getTestingOptions().getNumberUnboxerOptions().enable()) + .compile() + .inspect(this::assertUnboxing) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines("32", "33", "34", "35", "42", "43", "42", "43"); + } + + private void assertUnboxing(CodeInspector codeInspector) { + ClassSubject mainClass = codeInspector.clazz(Main.class); + assertThat(mainClass, isPresent()); + assertEquals(5, mainClass.allMethods().size()); + mainClass + .allMethods() + .forEach( + m -> { + if (!m.getOriginalMethodName().equals("main")) { + assertEquals(1, m.getParameters().size()); + assertTrue(m.getProgramMethod().getParameter(0).isIntType()); + } + }); + } + + static class Main { + + public static void main(String[] args) { + directPrintUnbox(31); + directPrintUnbox(32); + directPrintUnbox(33, 0); + directPrintUnbox(34, 1); + + forwardToPrint(41); + forwardToPrint(42); + forwardToPrint(41, 0); + forwardToPrint(42, 1); + } + + @NeverInline + private static void forwardToPrint(Integer boxed) { + directPrintUnbox(boxed); + } + + // Final proto should overlap with method above. + @NoParameterTypeStrengthening + @NeverInline + private static void forwardToPrint(int boxed, int unused) { + directPrintUnbox(boxed, unused); + } + + @NeverInline + private static void directPrintUnbox(Integer boxed) { + System.out.println(boxed + 1); + } + + // Final proto should overlap with method above. + @NoParameterTypeStrengthening + @NeverInline + private static void directPrintUnbox(int boxed, int unused) { + System.out.println(boxed + 1); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsNumberUnboxingTest.java index 135d38a..d5acd37 100644 --- a/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsNumberUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsNumberUnboxingTest.java
@@ -37,12 +37,11 @@ @Test public void testNumberUnboxing() throws Exception { - testForR8(parameters.getBackend()) + testForR8(parameters) .addInnerClasses(getClass()) .addKeepMainRule(Main.class) .enableInliningAnnotations() .addOptionsModification(opt -> opt.getTestingOptions().getNumberUnboxerOptions().enable()) - .setMinApi(parameters) .compile() .inspect(this::assertUnboxing) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsWideUnboxingMixNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsWideUnboxingMixNumberUnboxingTest.java new file mode 100644 index 0000000..4a03c52 --- /dev/null +++ b/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsWideUnboxingMixNumberUnboxingTest.java
@@ -0,0 +1,123 @@ +// Copyright (c) 2026, 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.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.MethodSubject; +import com.android.tools.r8.utils.internal.StringUtils; +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 StaticMethodsWideUnboxingMixNumberUnboxingTest extends TestBase { + + private final String EXPECTED_RESULT = + StringUtils.lines( + "1", "5", "9", "2", "6", "10", "4", "8", "12", "5", "9", "13", "11", "15", "19", "12", + "16", "110", "14", "18", "112", "15", "19", "113"); + + private final TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + public StaticMethodsWideUnboxingMixNumberUnboxingTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testNumberUnboxing() throws Exception { + testForR8(parameters) + .addInnerClasses(getClass()) + .addKeepMainRule(Main.class) + .enableInliningAnnotations() + .addOptionsModification(opt -> opt.getTestingOptions().getNumberUnboxerOptions().enable()) + .compile() + .inspect(this::assertUnboxing) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutput(EXPECTED_RESULT); + } + + private void assertUnboxing(CodeInspector codeInspector) { + ClassSubject mainClass = codeInspector.clazz(Main.class); + assertThat(mainClass, isPresent()); + + MethodSubject methodSubject = mainClass.uniqueMethodWithOriginalName("printI"); + assertThat(methodSubject, isPresent()); + assertTrue(methodSubject.getProgramMethod().getParameter(0).isIntType()); + + methodSubject = mainClass.uniqueMethodWithOriginalName("printL"); + assertThat(methodSubject, isPresent()); + assertTrue(methodSubject.getProgramMethod().getParameter(0).isLongType()); + + methodSubject = mainClass.uniqueMethodWithOriginalName("printAll"); + assertThat(methodSubject, isPresent()); + for (int i = 0; i < 12; i += 2) { + assertTrue(methodSubject.getProgramMethod().getParameter(i).isIntType()); + assertTrue(methodSubject.getProgramMethod().getParameter(i + 1).isLongType()); + } + } + + static class Main { + + public static void main(String[] args) { + printAll(1, 2, 3, 4L, 5, 6, 7, 8L, 9, 10, 11, 12L); + printAll(11, 12, 13, 14L, 15, 16, 17, 18L, 19, 110, 111, 112L); + } + + @NeverInline + private static void printAll( + int i, + long l, + Integer boxedI, + Long boxedL, + int i2, + long l2, + Integer boxedI2, + Long boxedL2, + int i3, + long l3, + Integer boxedI3, + Long boxedL3) { + + System.out.println(i); + System.out.println(i2); + System.out.println(i3); + System.out.println(l); + System.out.println(l2); + System.out.println(l3); + + printI(boxedI); + printI(boxedI2); + printI(boxedI3); + printL(boxedL); + printL(boxedL2); + printL(boxedL3); + } + + @NeverInline + private static void printI(Integer boxed) { + System.out.println(boxed + 1); + } + + @NeverInline + private static void printL(Long boxed) { + System.out.println(boxed + 1); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsWideUnboxingNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsWideUnboxingNumberUnboxingTest.java index c71b5bb..417ac5d 100644 --- a/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsWideUnboxingNumberUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/numberunboxing/StaticMethodsWideUnboxingNumberUnboxingTest.java
@@ -37,12 +37,11 @@ @Test public void testNumberUnboxing() throws Exception { - testForR8(parameters.getBackend()) + testForR8(parameters) .addInnerClasses(getClass()) .addKeepMainRule(Main.class) .enableInliningAnnotations() .addOptionsModification(opt -> opt.getTestingOptions().getNumberUnboxerOptions().enable()) - .setMinApi(parameters) .compile() .inspect(this::assertUnboxing) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsNumberUnboxingTest.java index 9e4d9ae..679393e 100644 --- a/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsNumberUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsNumberUnboxingTest.java
@@ -37,12 +37,11 @@ @Test public void testNumberUnboxing() throws Exception { - testForR8(parameters.getBackend()) + testForR8(parameters) .addInnerClasses(getClass()) .addKeepMainRule(Main.class) .enableInliningAnnotations() .addOptionsModification(opt -> opt.getTestingOptions().getNumberUnboxerOptions().enable()) - .setMinApi(parameters) .compile() .inspect(this::assertUnboxing) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsOverrideNumberUnboxingTest.java b/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsOverrideNumberUnboxingTest.java index 6105a6d..628d5d4 100644 --- a/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsOverrideNumberUnboxingTest.java +++ b/src/test/java/com/android/tools/r8/numberunboxing/VirtualMethodsOverrideNumberUnboxingTest.java
@@ -37,14 +37,13 @@ @Test public void testNumberUnboxing() throws Throwable { - testForR8(parameters.getBackend()) + testForR8(parameters) .addInnerClasses(getClass()) .addKeepMainRule(Main.class) .enableInliningAnnotations() .enableNoHorizontalClassMergingAnnotations() .enableNeverClassInliningAnnotations() .addOptionsModification(opt -> opt.getTestingOptions().getNumberUnboxerOptions().enable()) - .setMinApi(parameters) .compile() .inspect(this::assertUnboxing) .run(parameters.getRuntime(), Main.class)
diff --git a/src/test/java/com/android/tools/r8/processkeeprules/sanitychecks/ProcessKeepRulesSanityCheckTest.java b/src/test/java/com/android/tools/r8/processkeeprules/sanitychecks/ProcessKeepRulesSanityCheckTest.java index 1d2a23e..0b9b23b 100644 --- a/src/test/java/com/android/tools/r8/processkeeprules/sanitychecks/ProcessKeepRulesSanityCheckTest.java +++ b/src/test/java/com/android/tools/r8/processkeeprules/sanitychecks/ProcessKeepRulesSanityCheckTest.java
@@ -48,6 +48,9 @@ assertTrue(Files.exists(PROCESS_KEEP_RULES_JAR)); assertTrue(Files.exists(PROCESS_KEEP_RULES_MAP)); BooleanBox apiDatabaseSeen = new BooleanBox(); + BooleanBox apiMissingSeen = new BooleanBox(); + BooleanBox apiHiddenSeen = new BooleanBox(); + BooleanBox jarHiddenSeen = new BooleanBox(); BooleanBox licenseSeen = new BooleanBox(); BooleanBox manifestSeen = new BooleanBox(); BooleanBox versionSeen = new BooleanBox(); @@ -76,6 +79,12 @@ licenseSeen.set(); } else if (name.equals("resources/api_database.ser")) { apiDatabaseSeen.set(); + } else if (name.equals("resources/missing.api.txt")) { + apiMissingSeen.set(); + } else if (name.equals("resources/hidden.api.txt")) { + apiHiddenSeen.set(); + } else if (name.equals("resources/hidden.jar.txt")) { + jarHiddenSeen.set(); } else if (name.endsWith("/")) { fail("Unexpected directory entry '" + name + "'"); } else { @@ -84,6 +93,9 @@ }); assertTrue("Api database entry FOUND", apiDatabaseSeen.isFalse()); + assertTrue("Api missing entry FOUND", apiMissingSeen.isFalse()); + assertTrue("Api hidden entry FOUND", apiHiddenSeen.isFalse()); + assertTrue("Api jar missing entry FOUND", jarHiddenSeen.isFalse()); assertTrue("LICENSE entry NOT FOUND", licenseSeen.isTrue()); assertTrue("META-INF/MANIFEST.MF entry NOT FOUND", manifestSeen.isTrue()); assertTrue("com/android/tools/r8/Version.class entry NOT FOUND", versionSeen.isTrue());
diff --git a/src/test/java/com/android/tools/r8/redex/ReoptimizeToNativePcWithMappingTest.java b/src/test/java/com/android/tools/r8/redex/ReoptimizeToNativePcWithMappingTest.java new file mode 100644 index 0000000..c65cf3f --- /dev/null +++ b/src/test/java/com/android/tools/r8/redex/ReoptimizeToNativePcWithMappingTest.java
@@ -0,0 +1,157 @@ +// Copyright (c) 2026, 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.redex; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.CompilationMode; +import com.android.tools.r8.D8TestCompileResult; +import com.android.tools.r8.R8TestCompileResult; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.naming.retrace.StackTrace; +import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import com.android.tools.r8.utils.internal.Box; +import com.android.tools.r8.utils.internal.FileUtils; +import java.nio.file.Path; +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 ReoptimizeToNativePcWithMappingTest extends TestBase { + + private final TestParameters parameters; + + // Cinnamon Bun outputs DEX version 039 which is only supported on Android 9.0 (Pie) and newer. + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDexRuntimesStartingFromIncluding(Version.V9_0_0).build(); + } + + public ReoptimizeToNativePcWithMappingTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + AndroidApiLevel initialMinApi = AndroidApiLevel.N; + AndroidApiLevel reoptimizationMinApi = AndroidApiLevel.CINNAMON_BUN; + + Box<String> currentFooMethodName = new Box<>(); + Box<String> currentTestClassName = new Box<>(); + R8TestCompileResult r8CompileResult = + testForR8(Backend.DEX) + .setMinApi(initialMinApi) + .setMode(CompilationMode.RELEASE) + .addProgramClasses(StacktracePreservationTestClass.class) + .addKeepRules( + "-keep,allowrepackage class " + + StacktracePreservationTestClass.class.getName() + + " { public static void main(java.lang.String[]); }", + "-repackageclasses repackaged") + .enableInliningAnnotations() + .compile() + .inspect( + inspector -> { + ClassSubject clazz = + inspector.clazz( + Reference.classFromClass(StacktracePreservationTestClass.class)); + assertTrue(clazz.isPresent()); + assertTrue(clazz.getFinalName().startsWith("repackaged.")); + currentTestClassName.set(clazz.getFinalName()); + MethodSubject method = clazz.uniqueMethodWithOriginalName("foo"); + assertTrue(method.isPresent()); + currentFooMethodName.set(method.getFinalName()); + assertTrue("Expected debug info on N", method.hasLineNumberTable()); + }); + + Path r8Output = r8CompileResult.writeToZip(); + String r8Mapping = r8CompileResult.getProguardMap(); + assertThat( + r8Mapping, + containsString(StacktracePreservationTestClass.class.getName() + " -> repackaged.")); + + r8CompileResult + .run(parameters.getRuntime(), currentTestClassName.get()) + .assertFailureWithErrorThatThrows(RuntimeException.class) + .inspectOriginalStackTrace( + stacktrace -> { + assertThat(stacktrace, not(StackTrace.isSame(getExpectedStackTrace()))); + StackTrace retracedStackTrace = stacktrace.retrace(r8Mapping); + assertThat(retracedStackTrace, StackTrace.isSame(getExpectedStackTrace())); + }); + + Path inputMap = temp.newFolder().toPath().resolve("input.map"); + FileUtils.writeTextFile(inputMap, r8Mapping); + + D8TestCompileResult d8CompileResult = + testForD8(Backend.DEX) + .setMinApi(reoptimizationMinApi) + .setExperimentalReoptimizeDex(true) + .internalEnableMappingOutput() + .setMode(CompilationMode.RELEASE) + .apply(b -> b.getBuilder().setProguardMapInputFile(inputMap)) + .addProgramFiles(r8Output) + .compile() + .inspect( + inspector -> { + ClassSubject clazz = inspector.clazz(currentTestClassName.get()); + assertTrue(clazz.isPresent()); + MethodSubject method = + clazz.uniqueMethodWithOriginalName(currentFooMethodName.get()); + assertTrue(method.isPresent()); + assertNull( + "Expected native debug info due to mapping output", + method.getMethod().getCode().asDexCode().getDebugInfo()); + }); + + // Verify that the output mapping is correct and composed. + String d8Mapping = d8CompileResult.getProguardMap(); + assertThat( + d8Mapping, + containsString(StacktracePreservationTestClass.class.getName() + " -> repackaged.")); + + d8CompileResult + .run(parameters.getRuntime(), currentTestClassName.get()) + .assertFailureWithErrorThatThrows(RuntimeException.class) + .inspectOriginalStackTrace( + stacktrace -> { + assertThat(stacktrace, not(StackTrace.isSame(getExpectedStackTrace()))); + StackTrace retracedStackTrace = stacktrace.retrace(d8Mapping); + assertThat(retracedStackTrace, StackTrace.isSame(getExpectedStackTrace())); + }); + } + + private StackTrace getExpectedStackTrace() { + String className = StacktracePreservationTestClass.class.getName(); + return StackTrace.builder() + .add( + StackTraceLine.builder() + .setClassName(className) + .setMethodName("foo") + .setFileName("StacktracePreservationTestClass.java") + .setLineNumber(19) + .build()) + .add( + StackTraceLine.builder() + .setClassName(className) + .setMethodName("main") + .setFileName("StacktracePreservationTestClass.java") + .setLineNumber(12) + .build()) + .build(); + } +}
diff --git a/src/test/java/com/android/tools/r8/utils/CliParserTest.java b/src/test/java/com/android/tools/r8/utils/CliParserTest.java index 7431575..960787d 100644 --- a/src/test/java/com/android/tools/r8/utils/CliParserTest.java +++ b/src/test/java/com/android/tools/r8/utils/CliParserTest.java
@@ -255,12 +255,12 @@ } @Test - public void testOverlapPrefixAndPrefix() { + public void testDuplicatePrefix() { CliParser<Builder> parser = new CliParser<>("Usage: test"); parser.prefix1("--test", "[key]", "<val>", "Prefix.", (b, s, v) -> {}); assertThrows( AssertionError.class, - () -> parser.prefix1("--test-now", "[key]", "<val>", "Prefix.", (b, s, v) -> {})); + () -> parser.prefix1("--test", "[key]", "<val>", "Prefix.", (b, s, v) -> {})); } @Test @@ -298,16 +298,56 @@ parser.parse(new String[] {"--flag:foo=bar"}, builder, errors::add); assertEquals(1, errors.size()); - assertEquals("Option --flag:foo does not take a value.", errors.get(0)); + assertEquals("Option --flag:foo does not take an argument.", errors.get(0)); assertTrue(suffixes.isEmpty()); } @Test - public void testOverlapPrefix0AndPrefix1() { + public void testPrefixWithAlternative() { CliParser<Builder> parser = new CliParser<>("Usage: test"); - parser.prefix0("--test", "[prop]", "Prefix 0.", (b, s) -> {}); - assertThrows( - AssertionError.class, - () -> parser.prefix1("--test-now", "[key]", "<val>", "Prefix 1.", (b, s, v) -> {})); + List<String> suffixes = new ArrayList<>(); + parser.prefix0( + "--force-enable-assertions", + "[:class]", + "Enable assertions.", + (b, suffix) -> suffixes.add(suffix), + "--force-ea"); + + Builder builder = new Builder(); + List<String> errors = new ArrayList<>(); + parser.parse( + new String[] {"--force-enable-assertions:com.example.Foo", "--force-ea:com.example.Bar"}, + builder, + errors::add); + + assertTrue(errors.isEmpty()); + assertEquals(ImmutableList.of(":com.example.Foo", ":com.example.Bar"), suffixes); + } + + @Test + public void testOverlappingPrefixResolution() { + CliParser<Builder> parser = new CliParser<>("Usage: test"); + List<String> matched = new ArrayList<>(); + parser.prefix0( + "--force-passthrough-assertions", + "[:class]", + "Passthrough assertions.", + (b, s) -> matched.add("long" + s), + "--force-pa"); + + Builder builder = new Builder(); + List<String> errors = new ArrayList<>(); + parser.parse( + new String[] { + "--force-passthrough-assertions:pkg...", + "--force-passthrough-assertions", + "--force-pa:pkg...", + "--force-pa" + }, + builder, + errors::add); + + assertTrue(errors.isEmpty()); + assertEquals(ImmutableList.of("long:pkg...", "long", "long:pkg...", "long"), matched); } }
diff --git a/src/test/java11/com/android/tools/r8/jdk9/backport/ArraysCompareUnsignedBackportJava9Test.java b/src/test/java11/com/android/tools/r8/jdk9/backport/ArraysCompareUnsignedBackportJava9Test.java new file mode 100644 index 0000000..a74c2c7 --- /dev/null +++ b/src/test/java11/com/android/tools/r8/jdk9/backport/ArraysCompareUnsignedBackportJava9Test.java
@@ -0,0 +1,347 @@ +// Copyright (c) 2026, 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.jdk9.backport; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.desugar.backports.AbstractBackportTest; +import com.android.tools.r8.utils.AndroidApiLevel; +import java.util.Arrays; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public final class ArraysCompareUnsignedBackportJava9Test extends AbstractBackportTest { + @Parameters(name = "{0}") + public static Iterable<?> data() { + return TestBase.getTestParameters() + .withCfRuntimesStartingFromIncluding(CfVm.JDK11) + .withDexRuntimes() + .withAllApiLevelsAlsoForCf() + .build(); + } + + public ArraysCompareUnsignedBackportJava9Test(TestParameters parameters) { + super(parameters, Arrays.class, Main.class); + + // Arrays.compareUnsigned methods were added in Android T (API 33). + registerTarget(AndroidApiLevel.T, 119); + } + + public static class Main extends MiniAssert { + + public static void main(String[] args) { + testCompareUnsignedByte(); + testCompareUnsignedByteRange(); + testCompareUnsignedShort(); + testCompareUnsignedShortRange(); + testCompareUnsignedInt(); + testCompareUnsignedIntRange(); + testCompareUnsignedLong(); + testCompareUnsignedLongRange(); + } + + private static void testCompareUnsignedByte() { + byte[] nullArray = System.currentTimeMillis() > 0 ? null : new byte[0]; + + assertTrue(Arrays.compareUnsigned(nullArray, nullArray) == 0); + assertTrue(Arrays.compareUnsigned(nullArray, new byte[0]) < 0); + assertTrue(Arrays.compareUnsigned(new byte[0], nullArray) > 0); + + byte[] a = new byte[] {1, 2, 3}; + assertTrue(Arrays.compareUnsigned(a, a) == 0); + assertTrue(Arrays.compareUnsigned(new byte[0], new byte[0]) == 0); + assertTrue(Arrays.compareUnsigned(new byte[] {1, 2, 3}, new byte[] {1, 2, 3}) == 0); + assertTrue( + Arrays.compareUnsigned( + new byte[] {Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE}, + new byte[] {Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE}) + == 0); + + assertTrue(Arrays.compareUnsigned(new byte[] {1, 2}, new byte[] {1, 2, 3}) < 0); + assertTrue(Arrays.compareUnsigned(new byte[] {1, 2, 3}, new byte[] {1, 2}) > 0); + + assertTrue(Arrays.compareUnsigned(new byte[] {0}, new byte[] {1}) < 0); + assertTrue(Arrays.compareUnsigned(new byte[] {1}, new byte[] {Byte.MAX_VALUE}) < 0); + assertTrue( + Arrays.compareUnsigned(new byte[] {Byte.MAX_VALUE}, new byte[] {Byte.MIN_VALUE}) < 0); + assertTrue(Arrays.compareUnsigned(new byte[] {Byte.MIN_VALUE}, new byte[] {(byte) -1}) < 0); + byte[] minusOne = new byte[] {(byte) -1}; + byte[] zero = new byte[] {0}; + byte[] max = new byte[] {Byte.MAX_VALUE}; + byte[] min = new byte[] {Byte.MIN_VALUE}; + assertTrue(Arrays.compareUnsigned(minusOne, zero) > 0); + assertTrue(Arrays.compareUnsigned(minusOne, max) > 0); + assertTrue(Arrays.compareUnsigned(minusOne, min) > 0); + assertTrue(Arrays.compareUnsigned(new byte[] {1, 2, (byte) -1}, new byte[] {1, 2, 0}) > 0); + } + + private static void testCompareUnsignedByteRange() { + byte[] nullArray = System.currentTimeMillis() > 0 ? null : new byte[0]; + byte[] a = new byte[] {-3, -2, -1, 0, 1, 2, 3}; + byte[] b = new byte[] {Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE}; + + assertTrue(Arrays.compareUnsigned(new byte[] {}, 0, 0, new byte[] {}, 0, 0) == 0); + assertTrue(Arrays.compareUnsigned(a, 0, 1, a, 0, 1) == 0); + assertTrue(Arrays.compareUnsigned(a, 2, 5, b, 1, 4) == 0); + assertTrue(Arrays.compareUnsigned(a, 1, 1, b, 2, 2) == 0); + + assertTrue(Arrays.compareUnsigned(a, 2, 3, b, 2, 3) > 0); + assertTrue(Arrays.compareUnsigned(b, 2, 3, a, 2, 3) < 0); + assertTrue(Arrays.compareUnsigned(a, 2, 4, b, 1, 4) < 0); + assertTrue(Arrays.compareUnsigned(b, 1, 4, a, 2, 4) > 0); + + try { + Arrays.compareUnsigned(nullArray, 0, 1, b, 1, 2); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.compareUnsigned(a, 0, 1, nullArray, 1, 2); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.compareUnsigned(a, 2, 1, b, 1, 2); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.compareUnsigned(a, -1, 1, b, 1, 2); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.compareUnsigned(a, 0, 10, b, 1, 2); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testCompareUnsignedShort() { + short[] nullArray = System.currentTimeMillis() > 0 ? null : new short[0]; + + assertTrue(Arrays.compareUnsigned(nullArray, nullArray) == 0); + assertTrue(Arrays.compareUnsigned(nullArray, new short[0]) < 0); + assertTrue(Arrays.compareUnsigned(new short[0], nullArray) > 0); + + short[] a = new short[] {1, 2, 3}; + assertTrue(Arrays.compareUnsigned(a, a) == 0); + assertTrue(Arrays.compareUnsigned(new short[0], new short[0]) == 0); + assertTrue(Arrays.compareUnsigned(new short[] {1, 2, 3}, new short[] {1, 2, 3}) == 0); + assertTrue( + Arrays.compareUnsigned( + new short[] {Short.MIN_VALUE, -1, 0, 1, Short.MAX_VALUE}, + new short[] {Short.MIN_VALUE, -1, 0, 1, Short.MAX_VALUE}) + == 0); + + assertTrue(Arrays.compareUnsigned(new short[] {1, 2}, new short[] {1, 2, 3}) < 0); + assertTrue(Arrays.compareUnsigned(new short[] {1, 2, 3}, new short[] {1, 2}) > 0); + + assertTrue(Arrays.compareUnsigned(new short[] {0}, new short[] {1}) < 0); + assertTrue(Arrays.compareUnsigned(new short[] {1}, new short[] {Short.MAX_VALUE}) < 0); + assertTrue( + Arrays.compareUnsigned(new short[] {Short.MAX_VALUE}, new short[] {Short.MIN_VALUE}) < 0); + short[] minusOne = new short[] {(short) -1}; + short[] zero = new short[] {0}; + short[] max = new short[] {Short.MAX_VALUE}; + short[] min = new short[] {Short.MIN_VALUE}; + assertTrue(Arrays.compareUnsigned(minusOne, zero) > 0); + assertTrue(Arrays.compareUnsigned(minusOne, max) > 0); + assertTrue(Arrays.compareUnsigned(minusOne, min) > 0); + assertTrue(Arrays.compareUnsigned(new short[] {1, 2, (short) -1}, new short[] {1, 2, 0}) > 0); + } + + private static void testCompareUnsignedShortRange() { + short[] nullArray = System.currentTimeMillis() > 0 ? null : new short[0]; + short[] a = new short[] {-3, -2, -1, 0, 1, 2, 3}; + short[] b = new short[] {Short.MIN_VALUE, -1, 0, 1, Short.MAX_VALUE}; + + assertTrue(Arrays.compareUnsigned(new short[] {}, 0, 0, new short[] {}, 0, 0) == 0); + assertTrue(Arrays.compareUnsigned(a, 0, 1, a, 0, 1) == 0); + assertTrue(Arrays.compareUnsigned(a, 2, 5, b, 1, 4) == 0); + assertTrue(Arrays.compareUnsigned(a, 1, 1, b, 2, 2) == 0); + + assertTrue(Arrays.compareUnsigned(a, 2, 3, b, 2, 3) > 0); + assertTrue(Arrays.compareUnsigned(b, 2, 3, a, 2, 3) < 0); + assertTrue(Arrays.compareUnsigned(a, 2, 4, b, 1, 4) < 0); + assertTrue(Arrays.compareUnsigned(b, 1, 4, a, 2, 4) > 0); + + try { + Arrays.compareUnsigned(nullArray, 0, 1, b, 1, 2); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.compareUnsigned(a, 0, 1, nullArray, 1, 2); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.compareUnsigned(a, 2, 1, b, 1, 2); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.compareUnsigned(a, -1, 1, b, 1, 2); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.compareUnsigned(a, 0, 10, b, 1, 2); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testCompareUnsignedInt() { + int[] nullArray = System.currentTimeMillis() > 0 ? null : new int[0]; + + assertTrue(Arrays.compareUnsigned(nullArray, nullArray) == 0); + assertTrue(Arrays.compareUnsigned(nullArray, new int[0]) < 0); + assertTrue(Arrays.compareUnsigned(new int[0], nullArray) > 0); + + int[] a = new int[] {1, 2, 3}; + assertTrue(Arrays.compareUnsigned(a, a) == 0); + assertTrue(Arrays.compareUnsigned(new int[0], new int[0]) == 0); + assertTrue(Arrays.compareUnsigned(new int[] {1, 2, 3}, new int[] {1, 2, 3}) == 0); + assertTrue( + Arrays.compareUnsigned( + new int[] {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE}, + new int[] {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE}) + == 0); + + assertTrue(Arrays.compareUnsigned(new int[] {1, 2}, new int[] {1, 2, 3}) < 0); + assertTrue(Arrays.compareUnsigned(new int[] {1, 2, 3}, new int[] {1, 2}) > 0); + + assertTrue(Arrays.compareUnsigned(new int[] {0}, new int[] {1}) < 0); + assertTrue(Arrays.compareUnsigned(new int[] {1}, new int[] {Integer.MAX_VALUE}) < 0); + assertTrue( + Arrays.compareUnsigned(new int[] {Integer.MAX_VALUE}, new int[] {Integer.MIN_VALUE}) < 0); + assertTrue(Arrays.compareUnsigned(new int[] {Integer.MIN_VALUE}, new int[] {-1}) < 0); + assertTrue(Arrays.compareUnsigned(new int[] {-1}, new int[] {0}) > 0); + assertTrue(Arrays.compareUnsigned(new int[] {-1}, new int[] {Integer.MAX_VALUE}) > 0); + assertTrue(Arrays.compareUnsigned(new int[] {-1}, new int[] {Integer.MIN_VALUE}) > 0); + assertTrue(Arrays.compareUnsigned(new int[] {1, 2, -1}, new int[] {1, 2, 0}) > 0); + } + + private static void testCompareUnsignedIntRange() { + int[] nullArray = System.currentTimeMillis() > 0 ? null : new int[0]; + int[] a = new int[] {-3, -2, -1, 0, 1, 2, 3}; + int[] b = new int[] {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE}; + + assertTrue(Arrays.compareUnsigned(new int[] {}, 0, 0, new int[] {}, 0, 0) == 0); + assertTrue(Arrays.compareUnsigned(a, 0, 1, a, 0, 1) == 0); + assertTrue(Arrays.compareUnsigned(a, 2, 5, b, 1, 4) == 0); + assertTrue(Arrays.compareUnsigned(a, 1, 1, b, 2, 2) == 0); + + assertTrue(Arrays.compareUnsigned(a, 2, 3, b, 2, 3) > 0); + assertTrue(Arrays.compareUnsigned(b, 2, 3, a, 2, 3) < 0); + assertTrue(Arrays.compareUnsigned(a, 2, 4, b, 1, 4) < 0); + assertTrue(Arrays.compareUnsigned(b, 1, 4, a, 2, 4) > 0); + + try { + Arrays.compareUnsigned(nullArray, 0, 1, b, 1, 2); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.compareUnsigned(a, 0, 1, nullArray, 1, 2); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.compareUnsigned(a, 2, 1, b, 1, 2); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.compareUnsigned(a, -1, 1, b, 1, 2); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.compareUnsigned(a, 0, 10, b, 1, 2); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testCompareUnsignedLong() { + long[] nullArray = System.currentTimeMillis() > 0 ? null : new long[0]; + + assertTrue(Arrays.compareUnsigned(nullArray, nullArray) == 0); + assertTrue(Arrays.compareUnsigned(nullArray, new long[0]) < 0); + assertTrue(Arrays.compareUnsigned(new long[0], nullArray) > 0); + + long[] a = new long[] {1L, 2L, 3L}; + assertTrue(Arrays.compareUnsigned(a, a) == 0); + assertTrue(Arrays.compareUnsigned(new long[0], new long[0]) == 0); + assertTrue(Arrays.compareUnsigned(new long[] {1L, 2L, 3L}, new long[] {1L, 2L, 3L}) == 0); + assertTrue( + Arrays.compareUnsigned( + new long[] {Long.MIN_VALUE, -1L, 0L, 1L, Long.MAX_VALUE}, + new long[] {Long.MIN_VALUE, -1L, 0L, 1L, Long.MAX_VALUE}) + == 0); + + assertTrue(Arrays.compareUnsigned(new long[] {1L, 2L}, new long[] {1L, 2L, 3L}) < 0); + assertTrue(Arrays.compareUnsigned(new long[] {1L, 2L, 3L}, new long[] {1L, 2L}) > 0); + + assertTrue(Arrays.compareUnsigned(new long[] {0L}, new long[] {1L}) < 0); + assertTrue(Arrays.compareUnsigned(new long[] {1L}, new long[] {Long.MAX_VALUE}) < 0); + assertTrue( + Arrays.compareUnsigned(new long[] {Long.MAX_VALUE}, new long[] {Long.MIN_VALUE}) < 0); + assertTrue(Arrays.compareUnsigned(new long[] {Long.MIN_VALUE}, new long[] {-1L}) < 0); + assertTrue(Arrays.compareUnsigned(new long[] {-1L}, new long[] {0L}) > 0); + assertTrue(Arrays.compareUnsigned(new long[] {-1L}, new long[] {Long.MAX_VALUE}) > 0); + assertTrue(Arrays.compareUnsigned(new long[] {-1L}, new long[] {Long.MIN_VALUE}) > 0); + assertTrue(Arrays.compareUnsigned(new long[] {1L, 2L, -1L}, new long[] {1L, 2L, 0L}) > 0); + } + + private static void testCompareUnsignedLongRange() { + long[] nullArray = System.currentTimeMillis() > 0 ? null : new long[0]; + long[] a = new long[] {-3L, -2L, -1L, 0L, 1L, 2L, 3L}; + long[] b = new long[] {Long.MIN_VALUE, -1L, 0L, 1L, Long.MAX_VALUE}; + + assertTrue(Arrays.compareUnsigned(new long[] {}, 0, 0, new long[] {}, 0, 0) == 0); + assertTrue(Arrays.compareUnsigned(a, 0, 1, a, 0, 1) == 0); + assertTrue(Arrays.compareUnsigned(a, 2, 5, b, 1, 4) == 0); + assertTrue(Arrays.compareUnsigned(a, 1, 1, b, 2, 2) == 0); + + assertTrue(Arrays.compareUnsigned(a, 2, 3, b, 2, 3) > 0); + assertTrue(Arrays.compareUnsigned(b, 2, 3, a, 2, 3) < 0); + assertTrue(Arrays.compareUnsigned(a, 2, 4, b, 1, 4) < 0); + assertTrue(Arrays.compareUnsigned(b, 1, 4, a, 2, 4) > 0); + + try { + Arrays.compareUnsigned(nullArray, 0, 1, b, 1, 2); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.compareUnsigned(a, 0, 1, nullArray, 1, 2); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.compareUnsigned(a, 2, 1, b, 1, 2); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.compareUnsigned(a, -1, 1, b, 1, 2); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.compareUnsigned(a, 0, 10, b, 1, 2); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + } +}
diff --git a/src/test/java11/com/android/tools/r8/jdk9/backport/ArraysMismatchBackportJava9Test.java b/src/test/java11/com/android/tools/r8/jdk9/backport/ArraysMismatchBackportJava9Test.java new file mode 100644 index 0000000..b7af956 --- /dev/null +++ b/src/test/java11/com/android/tools/r8/jdk9/backport/ArraysMismatchBackportJava9Test.java
@@ -0,0 +1,743 @@ +// Copyright (c) 2026, 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.jdk9.backport; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.desugar.backports.AbstractBackportTest; +import com.android.tools.r8.utils.AndroidApiLevel; +import java.util.Arrays; +import java.util.Comparator; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public final class ArraysMismatchBackportJava9Test extends AbstractBackportTest { + @Parameters(name = "{0}") + public static Iterable<?> data() { + return TestBase.getTestParameters() + .withCfRuntimesStartingFromIncluding(CfVm.JDK11) + .withDexRuntimes() + .withAllApiLevelsAlsoForCf() + .build(); + } + + public ArraysMismatchBackportJava9Test(TestParameters parameters) { + super(parameters, Arrays.class, Main.class); + + // Arrays.mismatch methods were added in Android T (API 33). + registerTarget(AndroidApiLevel.T, 202); + } + + public static class Main extends MiniAssert { + + public static void main(String[] args) { + testMismatchBoolean(); + testMismatchBooleanRange(); + testMismatchByte(); + testMismatchByteRange(); + testMismatchChar(); + testMismatchCharRange(); + testMismatchShort(); + testMismatchShortRange(); + testMismatchInt(); + testMismatchIntRange(); + testMismatchLong(); + testMismatchLongRange(); + testMismatchFloat(); + testMismatchFloatRange(); + testMismatchDouble(); + testMismatchDoubleRange(); + testMismatchObject(); + testMismatchObjectRange(); + testMismatchComparator(); + testMismatchComparatorRange(); + } + + private static void testMismatchBoolean() { + boolean[] a = new boolean[] {true, false, true}; + boolean[] b = new boolean[] {true, false, true}; + boolean[] c = new boolean[] {true, true, true}; + boolean[] d = new boolean[] {true, false}; + boolean[] empty = new boolean[0]; + boolean[] nullArray = System.currentTimeMillis() > 0 ? null : new boolean[0]; + + assertEquals(-1, Arrays.mismatch(a, a)); + assertEquals(-1, Arrays.mismatch(empty, empty)); + assertEquals(-1, Arrays.mismatch(a, b)); + assertEquals(1, Arrays.mismatch(a, c)); + assertEquals(2, Arrays.mismatch(a, d)); + assertEquals(2, Arrays.mismatch(d, a)); + assertEquals(0, Arrays.mismatch(new boolean[] {true}, new boolean[] {false})); + + try { + Arrays.mismatch(nullArray, b); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchBooleanRange() { + boolean[] a = new boolean[] {false, true, false, true, false}; + boolean[] b = new boolean[] {true, true, false, true, true}; + boolean[] nullArray = System.currentTimeMillis() > 0 ? null : new boolean[0]; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testMismatchByte() { + byte[] a = new byte[] {1, 2, 3}; + byte[] b = new byte[] {1, 2, 3}; + byte[] c = new byte[] {1, 4, 3}; + byte[] d = new byte[] {1, 2}; + byte[] empty = new byte[0]; + byte[] nullArray = System.currentTimeMillis() > 0 ? null : new byte[0]; + + assertEquals(-1, Arrays.mismatch(a, a)); + assertEquals(-1, Arrays.mismatch(empty, empty)); + assertEquals(-1, Arrays.mismatch(a, b)); + assertEquals(1, Arrays.mismatch(a, c)); + assertEquals(2, Arrays.mismatch(a, d)); + assertEquals(2, Arrays.mismatch(d, a)); + assertEquals(0, Arrays.mismatch(new byte[] {1}, new byte[] {2})); + + try { + Arrays.mismatch(nullArray, b); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchByteRange() { + byte[] a = new byte[] {0, 1, 2, 3, 0}; + byte[] b = new byte[] {9, 1, 2, 3, 9}; + byte[] nullArray = System.currentTimeMillis() > 0 ? null : new byte[0]; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testMismatchChar() { + char[] a = new char[] {'a', 'b', 'c'}; + char[] b = new char[] {'a', 'b', 'c'}; + char[] c = new char[] {'a', 'x', 'c'}; + char[] d = new char[] {'a', 'b'}; + char[] empty = new char[0]; + char[] nullArray = System.currentTimeMillis() > 0 ? null : new char[0]; + + assertEquals(-1, Arrays.mismatch(a, a)); + assertEquals(-1, Arrays.mismatch(empty, empty)); + assertEquals(-1, Arrays.mismatch(a, b)); + assertEquals(1, Arrays.mismatch(a, c)); + assertEquals(2, Arrays.mismatch(a, d)); + assertEquals(2, Arrays.mismatch(d, a)); + assertEquals(0, Arrays.mismatch(new char[] {'a'}, new char[] {'z'})); + + try { + Arrays.mismatch(nullArray, b); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchCharRange() { + char[] a = new char[] {'x', 'a', 'b', 'c', 'x'}; + char[] b = new char[] {'y', 'a', 'b', 'c', 'y'}; + char[] nullArray = System.currentTimeMillis() > 0 ? null : new char[0]; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testMismatchShort() { + short[] a = new short[] {1, 2, 3}; + short[] b = new short[] {1, 2, 3}; + short[] c = new short[] {1, 4, 3}; + short[] d = new short[] {1, 2}; + short[] empty = new short[0]; + short[] nullArray = System.currentTimeMillis() > 0 ? null : new short[0]; + + assertEquals(-1, Arrays.mismatch(a, a)); + assertEquals(-1, Arrays.mismatch(empty, empty)); + assertEquals(-1, Arrays.mismatch(a, b)); + assertEquals(1, Arrays.mismatch(a, c)); + assertEquals(2, Arrays.mismatch(a, d)); + assertEquals(2, Arrays.mismatch(d, a)); + assertEquals(0, Arrays.mismatch(new short[] {1}, new short[] {2})); + + try { + Arrays.mismatch(nullArray, b); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchShortRange() { + short[] a = new short[] {0, 1, 2, 3, 0}; + short[] b = new short[] {9, 1, 2, 3, 9}; + short[] nullArray = System.currentTimeMillis() > 0 ? null : new short[0]; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testMismatchInt() { + int[] a = new int[] {1, 2, 3}; + int[] b = new int[] {1, 2, 3}; + int[] c = new int[] {1, 4, 3}; + int[] d = new int[] {1, 2}; + int[] empty = new int[0]; + int[] nullArray = System.currentTimeMillis() > 0 ? null : new int[0]; + + assertEquals(-1, Arrays.mismatch(a, a)); + assertEquals(-1, Arrays.mismatch(empty, empty)); + assertEquals(-1, Arrays.mismatch(a, b)); + assertEquals(1, Arrays.mismatch(a, c)); + assertEquals(2, Arrays.mismatch(a, d)); + assertEquals(2, Arrays.mismatch(d, a)); + assertEquals(0, Arrays.mismatch(new int[] {1}, new int[] {2})); + + try { + Arrays.mismatch(nullArray, b); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchIntRange() { + int[] a = new int[] {0, 1, 2, 3, 0}; + int[] b = new int[] {9, 1, 2, 3, 9}; + int[] nullArray = System.currentTimeMillis() > 0 ? null : new int[0]; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testMismatchLong() { + long[] a = new long[] {1L, 2L, 3L}; + long[] b = new long[] {1L, 2L, 3L}; + long[] c = new long[] {1L, 4L, 3L}; + long[] d = new long[] {1L, 2L}; + long[] empty = new long[0]; + long[] nullArray = System.currentTimeMillis() > 0 ? null : new long[0]; + + assertEquals(-1, Arrays.mismatch(a, a)); + assertEquals(-1, Arrays.mismatch(empty, empty)); + assertEquals(-1, Arrays.mismatch(a, b)); + assertEquals(1, Arrays.mismatch(a, c)); + assertEquals(2, Arrays.mismatch(a, d)); + assertEquals(2, Arrays.mismatch(d, a)); + assertEquals(0, Arrays.mismatch(new long[] {1L}, new long[] {2L})); + + try { + Arrays.mismatch(nullArray, b); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchLongRange() { + long[] a = new long[] {0L, 1L, 2L, 3L, 0L}; + long[] b = new long[] {9L, 1L, 2L, 3L, 9L}; + long[] nullArray = System.currentTimeMillis() > 0 ? null : new long[0]; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testMismatchFloat() { + float[] a = new float[] {1.0f, Float.NaN, 3.0f, 0.0f}; + float[] b = new float[] {1.0f, Float.NaN, 3.0f, 0.0f}; + float[] c = new float[] {1.0f, Float.NaN, 3.0f, -0.0f}; + float[] d = new float[] {1.0f, Float.NaN}; + float[] empty = new float[0]; + float[] nullArray = System.currentTimeMillis() > 0 ? null : new float[0]; + + assertEquals(-1, Arrays.mismatch(a, a)); + assertEquals(-1, Arrays.mismatch(empty, empty)); + assertEquals(-1, Arrays.mismatch(a, b)); + assertEquals(3, Arrays.mismatch(a, c)); // 0.0f != -0.0f in bit comparison + assertEquals(2, Arrays.mismatch(a, d)); + assertEquals(2, Arrays.mismatch(d, a)); + assertEquals(-1, Arrays.mismatch(new float[] {Float.NaN}, new float[] {Float.NaN})); + + try { + Arrays.mismatch(nullArray, b); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchFloatRange() { + float[] a = new float[] {0.0f, 1.0f, Float.NaN, 3.0f, 0.0f}; + float[] b = new float[] {9.0f, 1.0f, Float.NaN, 3.0f, 9.0f}; + float[] nullArray = System.currentTimeMillis() > 0 ? null : new float[0]; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testMismatchDouble() { + double[] a = new double[] {1.0, Double.NaN, 3.0, 0.0}; + double[] b = new double[] {1.0, Double.NaN, 3.0, 0.0}; + double[] c = new double[] {1.0, Double.NaN, 3.0, -0.0}; + double[] d = new double[] {1.0, Double.NaN}; + double[] empty = new double[0]; + double[] nullArray = System.currentTimeMillis() > 0 ? null : new double[0]; + + assertEquals(-1, Arrays.mismatch(a, a)); + assertEquals(-1, Arrays.mismatch(empty, empty)); + assertEquals(-1, Arrays.mismatch(a, b)); + assertEquals(3, Arrays.mismatch(a, c)); // 0.0 != -0.0 in bit comparison + assertEquals(2, Arrays.mismatch(a, d)); + assertEquals(2, Arrays.mismatch(d, a)); + assertEquals(-1, Arrays.mismatch(new double[] {Double.NaN}, new double[] {Double.NaN})); + + try { + Arrays.mismatch(nullArray, b); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchDoubleRange() { + double[] a = new double[] {0.0, 1.0, Double.NaN, 3.0, 0.0}; + double[] b = new double[] {9.0, 1.0, Double.NaN, 3.0, 9.0}; + double[] nullArray = System.currentTimeMillis() > 0 ? null : new double[0]; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testMismatchObject() { + Object[] a = new Object[] {"a", null, "c"}; + Object[] b = new Object[] {"a", null, "c"}; + Object[] c = new Object[] {"a", "b", "c"}; + Object[] d = new Object[] {"a", null}; + Object[] empty = new Object[0]; + Object[] nullArray = System.currentTimeMillis() > 0 ? null : new Object[0]; + + assertEquals(-1, Arrays.mismatch(a, a)); + assertEquals(-1, Arrays.mismatch(empty, empty)); + assertEquals(-1, Arrays.mismatch(a, b)); + assertEquals(1, Arrays.mismatch(a, c)); + assertEquals(2, Arrays.mismatch(a, d)); + assertEquals(2, Arrays.mismatch(d, a)); + assertEquals(0, Arrays.mismatch(new Object[] {"x"}, new Object[] {"y"})); + + try { + Arrays.mismatch(nullArray, b); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchObjectRange() { + Object[] a = new Object[] {"x", "a", null, "c", "x"}; + Object[] b = new Object[] {"y", "a", null, "c", "y"}; + Object[] nullArray = System.currentTimeMillis() > 0 ? null : new Object[0]; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + + private static void testMismatchComparator() { + String[] a = new String[] {"a", "B", "c"}; + String[] b = new String[] {"A", "b", "C"}; + String[] c = new String[] {"a", "b", "x"}; + String[] d = new String[] {"a", "b"}; + String[] empty = new String[0]; + String[] nullArray = System.currentTimeMillis() > 0 ? null : new String[0]; + Comparator<String> caseInsensitive = String.CASE_INSENSITIVE_ORDER; + + assertEquals(-1, Arrays.mismatch(a, a, caseInsensitive)); + assertEquals(-1, Arrays.mismatch(empty, empty, caseInsensitive)); + assertEquals(-1, Arrays.mismatch(a, b, caseInsensitive)); + assertEquals(2, Arrays.mismatch(a, c, caseInsensitive)); + assertEquals(2, Arrays.mismatch(a, d, caseInsensitive)); + assertEquals(2, Arrays.mismatch(d, a, caseInsensitive)); + assertEquals(0, Arrays.mismatch(new String[] {"a"}, new String[] {"z"}, caseInsensitive)); + + try { + Arrays.mismatch(nullArray, b, caseInsensitive); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, nullArray, caseInsensitive); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, b, null); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + } + + private static void testMismatchComparatorRange() { + String[] a = new String[] {"x", "a", "B", "c", "x"}; + String[] b = new String[] {"y", "A", "b", "C", "y"}; + String[] nullArray = System.currentTimeMillis() > 0 ? null : new String[0]; + Comparator<String> caseInsensitive = String.CASE_INSENSITIVE_ORDER; + + assertEquals(-1, Arrays.mismatch(a, 0, 0, b, 0, 0, caseInsensitive)); + assertEquals(-1, Arrays.mismatch(a, 1, 4, b, 1, 4, caseInsensitive)); + assertEquals(-1, Arrays.mismatch(a, 0, 5, a, 0, 5, caseInsensitive)); + assertEquals(0, Arrays.mismatch(a, 0, 3, b, 0, 3, caseInsensitive)); + assertEquals(2, Arrays.mismatch(a, 1, 3, b, 1, 4, caseInsensitive)); + assertEquals(2, Arrays.mismatch(b, 1, 4, a, 1, 3, caseInsensitive)); + + try { + Arrays.mismatch(nullArray, 0, 1, b, 0, 1, caseInsensitive); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, nullArray, 0, 1, caseInsensitive); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 0, 1, b, 0, 1, null); + fail("Expected NullPointerException"); + } catch (NullPointerException expected) { + } + try { + Arrays.mismatch(a, 2, 1, b, 0, 1, caseInsensitive); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + } + try { + Arrays.mismatch(a, -1, 1, b, 0, 1, caseInsensitive); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + try { + Arrays.mismatch(a, 0, 10, b, 0, 1, caseInsensitive); + fail("Expected ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException expected) { + } + } + } +}
diff --git a/src/test/java8/apimodel/com/android/tools/r8/ApiDatabaseGeneratorTestHelper.java b/src/test/java8/apimodel/com/android/tools/r8/ApiDatabaseGeneratorTestHelper.java new file mode 100644 index 0000000..e190e8a --- /dev/null +++ b/src/test/java8/apimodel/com/android/tools/r8/ApiDatabaseGeneratorTestHelper.java
@@ -0,0 +1,18 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8; + +import com.android.tools.r8.apimodel.ParsedApiClass; +import com.android.tools.r8.apimodel.ParsedApiClassTrimming.JarTrimmer; +import com.android.tools.r8.apimodel.ParsedApiClassTrimming.Trimmer; +import java.util.Collection; +import java.util.function.Function; + +public class ApiDatabaseGeneratorTestHelper { + public static <E extends Throwable> Collection<ParsedApiClass> generateClasses( + ApiDatabaseGeneratorCommand command, Function<JarTrimmer, Trimmer<E>> jarTrimmerWrapper) + throws ApiDatabaseGeneratorException, E { + return ApiDatabaseGenerator.generateClasses(command, jarTrimmerWrapper); + } +}
diff --git a/src/test/java8/apimodel/com/android/tools/r8/androidapi/GenerateApiAmendments.java b/src/test/java8/apimodel/com/android/tools/r8/androidapi/GenerateApiAmendments.java new file mode 100644 index 0000000..b9dc369 --- /dev/null +++ b/src/test/java8/apimodel/com/android/tools/r8/androidapi/GenerateApiAmendments.java
@@ -0,0 +1,271 @@ +// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.androidapi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.references.ClassReference; +import com.android.tools.r8.references.FieldReference; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.internal.FileUtils; +import com.android.tools.r8.utils.internal.StringUtils; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class GenerateApiAmendments extends TestBase { + + private static final String API_MISSING_FILE = "missing.api.txt"; + private static final String API_HIDDEN_FILE = "hidden.api.txt"; + private static final String JAR_HIDDEN_FILE = "hidden.jar.txt"; + + private static Path getOutputDir() { + return Paths.get(ToolHelper.SOURCE_DIR, "main", "resources", "resources"); + } + + private static final Charset CHARSET = StandardCharsets.UTF_8; + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withNoneRuntime().build(); + } + + @Test + public void testGeneratedFilesUpToDate() throws Exception { + Path outputDir = getOutputDir(); + assertEquals( + FileUtils.readTextFile(outputDir.resolve(API_MISSING_FILE), CHARSET), generateApiMissing()); + assertEquals( + FileUtils.readTextFile(outputDir.resolve(API_HIDDEN_FILE), CHARSET), generateApiHidden()); + assertEquals( + FileUtils.readTextFile(outputDir.resolve(JAR_HIDDEN_FILE), CHARSET), generateJarHidden()); + } + + public static void main(String[] args) throws IOException { + // ----------------------------------------- IMPORTANT ----------------------------------------- + // When these amendments are semantically changed (i.e. a format change doesn't matter) + // DATABASE_FORMAT_VERSION must be incremented to reflect that the output of + // ApiDatabaseGenerator has changed. + // ----------------------------------------- IMPORTANT ----------------------------------------- + Path outputDir = getOutputDir(); + if (!Files.exists(outputDir)) { + Files.createDirectories(outputDir); + } + Files.write(outputDir.resolve(API_MISSING_FILE), generateApiMissing().getBytes(CHARSET)); + Files.write(outputDir.resolve(API_HIDDEN_FILE), generateApiHidden().getBytes(CHARSET)); + Files.write(outputDir.resolve(JAR_HIDDEN_FILE), generateJarHidden().getBytes(CHARSET)); + } + + private static String generateApiMissing() { + return makeFileContent(generateApiMissingLines()); + } + + public static String generateApiHidden() throws IOException { + return makeFileContent(generateApiHiddenLines()); + } + + public static String generateJarHidden() throws IOException { + return makeFileContent(generateJarHiddenLines()); + } + + private static String makeFileContent(List<String> lines) { + // Since this is a stable internal resource, line endings should NOT be platform dependent. + String header = + StringUtils.join( + "\n", + ImmutableList.of( + "// Copyright (c) 2026, 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.", + "//", + "// This file is generated by GenerateApiAmendments.java.", + "// Do not edit manually.")); + String content = StringUtils.join("\n", lines); + return header + "\n" + content + "\n"; + } + + private static List<String> generateApiMissingLines() { + ArrayList<String> lines = new ArrayList<>(); + AndroidApiLevelDatabaseTestHelper.visitAdditionalKnownApiReferences( + (methodReference, apiLevel) -> lines.add(formatApiMethod(methodReference, apiLevel))); + Collections.sort(lines); + return lines; + } + + /** See {@link com.android.tools.r8.apimodel.ApiAmendmentsParser} for the format. */ + private static String formatApiClass(ClassReference classReference, AndroidApiLevel apiLevel) { + return "class " + classReference.getDescriptor() + " " + apiLevel.getLevel(); + } + + /** See {@link com.android.tools.r8.apimodel.ApiAmendmentsParser} for the format. */ + private static String formatApiMethod(MethodReference methodReference, AndroidApiLevel apiLevel) { + String holder = methodReference.getHolderClass().getDescriptor(); + String methodName = methodReference.getMethodName(); + String descriptor = methodReference.getMethodDescriptor(); + return "method " + holder + " " + methodName + " " + descriptor + " " + apiLevel.getLevel(); + } + + /** See {@link com.android.tools.r8.apimodel.ApiAmendmentsParser} for the format. */ + private static String formatApiField(FieldReference fieldReference, AndroidApiLevel apiLevel) { + String holder = fieldReference.getHolderClass().getDescriptor(); + String name = fieldReference.getFieldName(); + return "field " + holder + " " + name + " " + apiLevel.getLevel(); + } + + private static List<String> generateJarHiddenLines() throws IOException { + ArrayList<String> lines = new ArrayList<>(); + Map<String, Boolean> skipClasses = getSkipClassesMap(); + + AndroidApiLevelDatabaseTestHelper.visitHiddenReferences( + (classReference, superReference, interfaces, apiLevel) -> { + if (checkForSkip(classReference.getBinaryName(), skipClasses)) { + return; + } + if (superReference != null) { + lines.add(formatJarClass(classReference, superReference, interfaces)); + } else { + lines.add(formatJarInterface(classReference, interfaces)); + } + }, + (methodReference, isStatic, apiLevel) -> { + if (checkForSkip(methodReference.getHolderClass().getBinaryName(), skipClasses)) { + return; + } + lines.add(formatJarMethod(methodReference, isStatic)); + }, + (fieldReference, apiLevel) -> { + if (checkForSkip(fieldReference.getHolderClass().getBinaryName(), skipClasses)) { + return; + } + lines.add(formatJarField(fieldReference)); + }); + ensureSkippedClasses(skipClasses); + Collections.sort(lines); + return lines; + } + + /** See {@link com.android.tools.r8.apimodel.ApiAmendmentsParser} for the format. */ + private static String formatJarClass( + ClassReference classReference, + ClassReference superReference, + List<ClassReference> interfaces) { + String clazz = classReference.getDescriptor(); + String superclass = superReference.getDescriptor(); + String implString = formatInheritances(interfaces); + return "class " + clazz + " extends " + superclass + " implements " + implString; + } + + /** See {@link com.android.tools.r8.apimodel.ApiAmendmentsParser} for the format. */ + private static String formatJarInterface( + ClassReference classReference, List<ClassReference> interfaces) { + String clazz = classReference.getDescriptor(); + String extString = formatInheritances(interfaces); + return "interface " + clazz + " extends " + extString; + } + + /** See {@link com.android.tools.r8.apimodel.ApiAmendmentsParser} for the format. */ + private static String formatInheritances(List<ClassReference> interfaces) { + String extString; + if (interfaces.isEmpty()) { + extString = "none"; + } else { + extString = + interfaces.stream() + .map(ClassReference::getDescriptor) + .sorted() + .collect(Collectors.joining(",")); + } + return extString; + } + + /** See {@link com.android.tools.r8.apimodel.ApiAmendmentsParser} for the format. */ + private static String formatJarMethod(MethodReference methodReference, Boolean isStatic) { + String holder = methodReference.getHolderClass().getDescriptor(); + String name = methodReference.getMethodName(); + String descriptor = methodReference.getMethodDescriptor(); + String virtualness = isStatic ? "static" : "instance"; + return "method " + holder + " " + name + " " + descriptor + " " + virtualness; + } + + /** See {@link com.android.tools.r8.apimodel.ApiAmendmentsParser} for the format. */ + private static String formatJarField(FieldReference fieldReference) { + String holder = fieldReference.getHolderClass().getDescriptor(); + String name = fieldReference.getFieldName(); + return "field " + holder + " " + name; + } + + private static List<String> generateApiHiddenLines() throws IOException { + ArrayList<String> lines = new ArrayList<>(); + Map<String, Boolean> skipClasses = getSkipClassesMap(); + + AndroidApiLevelDatabaseTestHelper.visitHiddenReferences( + (classReference, superReference, interfaces, apiLevel) -> { + if (checkForSkip(classReference.getBinaryName(), skipClasses)) { + return; + } + lines.add(formatApiClass(classReference, apiLevel)); + }, + (methodReference, isStatic, apiLevel) -> { + if (checkForSkip(methodReference.getHolderClass().getBinaryName(), skipClasses)) { + return; + } + lines.add(formatApiMethod(methodReference, apiLevel)); + }, + (fieldReference, apiLevel) -> { + if (checkForSkip(fieldReference.getHolderClass().getBinaryName(), skipClasses)) { + return; + } + lines.add(formatApiField(fieldReference, apiLevel)); + }); + ensureSkippedClasses(skipClasses); + Collections.sort(lines); + return lines; + } + + private static Map<String, Boolean> getSkipClassesMap() { + // These are present because of annotation inheritance, but they are not public classes. + Map<String, Boolean> skipClasses = new HashMap<>(); + skipClasses.put("java/nio/DirectByteBuffer", false); + return skipClasses; + } + + private static boolean checkForSkip(String holderBinaryName, Map<String, Boolean> skipClasses) { + boolean skip = false; + if (skipClasses.containsKey(holderBinaryName)) { + skipClasses.put(holderBinaryName, true); + skip = true; + } + return skip; + } + + private static void ensureSkippedClasses(Map<String, Boolean> skippedClasses) { + for (Map.Entry<String, Boolean> entry : skippedClasses.entrySet()) { + assertTrue(entry.getKey() + " was never skipped", entry.getValue()); + } + } +}
diff --git a/src/test/java8/apimodel/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java b/src/test/java8/apimodel/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java index 8313663..fe80e6b 100644 --- a/src/test/java8/apimodel/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java +++ b/src/test/java8/apimodel/com/android/tools/r8/apimodel/AndroidApiHashingDatabaseBuilderGeneratorTest.java
@@ -12,13 +12,13 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import com.android.tools.r8.ApiDatabaseGenerator; +import com.android.tools.r8.ApiDatabaseGeneratorCommand; import com.android.tools.r8.ApiDatabaseGeneratorException; -import com.android.tools.r8.DiagnosticsMatcher; +import com.android.tools.r8.ApiDatabaseGeneratorTestHelper; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestDiagnosticMessagesImpl; import com.android.tools.r8.TestParameters; @@ -26,19 +26,16 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.androidapi.AndroidApiLevelCompute; import com.android.tools.r8.androidapi.AndroidApiLevelCompute.DefaultAndroidApiLevelCompute; -import com.android.tools.r8.androidapi.AndroidApiLevelDatabaseTestHelper; import com.android.tools.r8.androidapi.AndroidApiLevelHashingDatabaseImpl; import com.android.tools.r8.androidapi.ApiDatabaseEntry; import com.android.tools.r8.androidapi.ComputedApiLevel; import com.android.tools.r8.androidapi.SunMiscUnsafeApiTest; -import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsingException; import com.android.tools.r8.apimodel.ParsedApiClassTrimming.JarTrimmer; import com.android.tools.r8.apimodel.ParsedApiClassTrimming.ListeningDecorator; import com.android.tools.r8.apimodel.ParsedApiClassTrimming.SkipAnswer; +import com.android.tools.r8.apimodel.ParsedApiClassTrimming.Trimmer; import com.android.tools.r8.apimodel.ParsedApiClassTrimming.TrimmerListener; -import com.android.tools.r8.apimodel.jar.ApiClassInfo; import com.android.tools.r8.apimodel.jar.ApiJarInfo; -import com.android.tools.r8.apimodel.jar.ApiJarReader; import com.android.tools.r8.graph.AppInfoWithClassHierarchy; import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; @@ -56,28 +53,22 @@ import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.internal.BiConsumerUtils; -import com.android.tools.r8.utils.internal.ListUtils; import com.android.tools.r8.utils.internal.TriConsumerUtils; -import com.android.tools.r8.utils.internal.collections.Pair; import com.android.tools.r8.utils.timing.Timing; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; -import org.hamcrest.CoreMatchers; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; @@ -105,24 +96,6 @@ this.parameters = parameters; } - private static class GenerateDatabaseResourceFilesResult { - - private final Path apiLevels; - - public GenerateDatabaseResourceFilesResult(Path apiLevels) { - this.apiLevels = apiLevels; - } - } - - private static GenerateDatabaseResourceFilesResult generateResourcesFiles( - Map<ApiDatabaseEntry, AndroidApiLevel> databaseEntries) throws Exception { - TemporaryFolder temp1 = new TemporaryFolder(); - temp1.create(); - Path apiLevels = temp1.newFile("api_levels.ser").toPath(); - AndroidApiHashingDatabaseBuilderGenerator.writeEntries(databaseEntries, apiLevels); - return new GenerateDatabaseResourceFilesResult(apiLevels); - } - private static Map<ApiDatabaseEntry, AndroidApiLevel> computeEntries( Collection<ParsedApiClass> apiClasses) throws Exception { Map<ApiDatabaseEntry, AndroidApiLevel> databaseEntries = @@ -131,90 +104,25 @@ return databaseEntries; } - private static void saveToTmp(Collection<ParsedApiClass> classes, String fileName) - throws IOException { - Path tmpDir = Paths.get(ToolHelper.getProjectRoot(), "tmp"); - if (!Files.exists(tmpDir)) { - Files.createDirectories(tmpDir); - } - Path file = tmpDir.resolve(fileName); - Files.write(file, sortAndFormat(classes)); - } - - public static List<String> sortAndFormat(Collection<ParsedApiClass> classes) { - List<ParsedApiClass> sortedClasses = new ArrayList<>(classes); - sortedClasses.sort(Comparator.comparing(c -> c.getClassReference().getDescriptor())); - List<String> result = new ArrayList<>(); - for (ParsedApiClass apiClass : sortedClasses) { - result.add(apiClass.getClassReference().getDescriptor() + " " + apiClass.getRange()); - List<Pair<ClassReference, ApiRange>> sortedSupertypes = new ArrayList<>(); - apiClass.forEachSupertype( - (reference, apiRange) -> sortedSupertypes.add(new Pair<>(reference, apiRange))); - sortedSupertypes.sort(Comparator.comparing(p -> p.getFirst().getDescriptor())); - for (Pair<ClassReference, ApiRange> pair : sortedSupertypes) { - result.add(" super: " + pair.getFirst().getDescriptor() + " " + pair.getSecond()); - } - List<Pair<ClassReference, ApiRange>> sortedInterfaces = new ArrayList<>(); - apiClass.forEachInterface( - (reference, apiRange) -> sortedInterfaces.add(new Pair<>(reference, apiRange))); - sortedInterfaces.sort(Comparator.comparing(p -> p.getFirst().getDescriptor())); - for (Pair<ClassReference, ApiRange> pair : sortedInterfaces) { - result.add(" implements: " + pair.getFirst().getDescriptor() + " " + pair.getSecond()); - } - List<Pair<FieldTypelessReference, ApiRange>> sortedFields = new ArrayList<>(); - apiClass.forEachField( - (reference, apiRange) -> sortedFields.add(new Pair<>(reference, apiRange))); - sortedFields.sort(Comparator.comparing(p -> p.getFirst().toString())); - for (Pair<FieldTypelessReference, ApiRange> pair : sortedFields) { - result.add(" field: " + pair.getFirst().toString() + " " + pair.getSecond()); - } - List<Pair<MethodReference, ApiRange>> sortedMethods = new ArrayList<>(); - apiClass.forEachMethod( - (reference, apiRange) -> sortedMethods.add(new Pair<>(reference, apiRange))); - sortedMethods.sort(Comparator.comparing(p -> p.getFirst().toString())); - for (Pair<MethodReference, ApiRange> pair : sortedMethods) { - result.add(" method: " + pair.getFirst().toString() + " " + pair.getSecond()); - } - } - return result; - } - private static Collection<ParsedApiClass> cachedParsedApiClasses = null; - private static Collection<ParsedApiClass> loadParsedApiClasses() - throws ParsingException, ApiDatabaseGeneratorException, IOException { + private static Collection<ParsedApiClass> loadParsedApiClasses() throws Exception { if (cachedParsedApiClasses == null) { - TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); - Collection<ParsedApiClass> apiClasses = - AndroidApiVersionsXmlParser.parse(ToolHelper.getApiVersionsXmlFile(API_LEVEL)); - saveToTmp(apiClasses, "1_parsed.txt"); - ParsedApiClassVerifier.verify(apiClasses); - ApiJarInfo jarInfo = ApiJarReader.read(ImmutableList.of(ToolHelper.getAndroidJar(API_LEVEL))); - apiClasses = amendMissingData(apiClasses, jarInfo, diagnosticsHandler); - saveToTmp(apiClasses, "2_merged.txt"); - ParsedApiClassVerifier.verify(apiClasses); - apiClasses = ParsedApiClassFlattening.flatten(apiClasses); - saveToTmp(apiClasses, "3_flattened.txt"); - ParsedApiClassVerifier.verify(apiClasses); - apiClasses = - ParsedApiClassTrimming.trim(apiClasses, new ParsedApiClassTrimming.RemovedTrimmer()); - saveToTmp(apiClasses, "4_trimmed_removed.txt"); - ParsedApiClassVerifier.verify(apiClasses); - apiClasses = filterByJar(apiClasses, jarInfo); - saveToTmp(apiClasses, "5_trimmed_jar.txt"); - ParsedApiClassVerifier.verify(apiClasses); - diagnosticsHandler.assertAllDiagnosticsMatch( - DiagnosticsMatcher.diagnosticMessage(CoreMatchers.startsWith("Duplicate class "))); - cachedParsedApiClasses = apiClasses; + ApiDatabaseGeneratorCommand command = + ApiDatabaseGeneratorCommand.builder() + .addXmlPath(ToolHelper.getApiVersionsXmlFile(API_LEVEL)) + .addJarPath(ToolHelper.getAndroidJar(API_LEVEL)) + .build(); + cachedParsedApiClasses = + ApiDatabaseGeneratorTestHelper.generateClasses( + command, AndroidApiHashingDatabaseBuilderGeneratorTest::addJarAssertions); } return cachedParsedApiClasses; } - private static Collection<ParsedApiClass> filterByJar( - Collection<ParsedApiClass> apiClasses, ApiJarInfo jarInfo) - throws ApiDatabaseGeneratorException { - JarTrimmer jarTrimmer = new JarTrimmer(jarInfo); + private static Trimmer<ApiDatabaseGeneratorException> addJarAssertions(JarTrimmer jarTrimmer) { Map<String, Boolean> expectedTrimmedPackages = new HashMap<>(); + ApiJarInfo jarInfo = jarTrimmer.getJarInfo(); expectedTrimmedPackages.put("junit/", false); expectedTrimmedPackages.put("android/test/", false); expectedTrimmedPackages.put("com/android/internal/", false); @@ -276,124 +184,16 @@ SkipAnswer answer) { assertEquals("No fields should be skipped: " + field, SkipAnswer.KEEP, answer); } + + @Override + public void done() { + for (Map.Entry<String, Boolean> entry : expectedTrimmedPackages.entrySet()) { + assertTrue( + "Expected trimmed package not trimmed: " + entry.getKey(), entry.getValue()); + } + } }; - Collection<ParsedApiClass> trimResult = - ParsedApiClassTrimming.trim(apiClasses, new ListeningDecorator<>(jarTrimmer, listener)); - for (Map.Entry<String, Boolean> entry : expectedTrimmedPackages.entrySet()) { - assertTrue("Expected trimmed package not trimmed: " + entry.getKey(), entry.getValue()); - } - return trimResult; - } - - /** - * Returns API data missing from {@code api-versions.xml} and {@code jarInfo} is mutated with data - * missing from {@code android.jar}. - */ - private static Collection<ParsedApiClass> amendMissingData( - Collection<ParsedApiClass> apiClasses, - ApiJarInfo jarInfo, - TestDiagnosticMessagesImpl diagnosticsHandler) - throws ApiDatabaseGeneratorException, IOException { - Map<ClassReference, ApiRange> lookup = makeApiLookup(apiClasses); - Collection<ParsedApiClass> missingApi = missingVersionEntries(lookup, jarInfo); - Collection<ParsedApiClass> hiddenApi = addHiddenEntries(lookup, jarInfo); - return ParsedApiClassMerging.merge( - Iterables.concat(apiClasses, missingApi, hiddenApi), diagnosticsHandler); - } - - /** These entries exist in android.jar and runtime but are missing in api-versions.xml. */ - private static Collection<ParsedApiClass> missingVersionEntries( - Map<ClassReference, ApiRange> lookup, ApiJarInfo jarInfoForVerification) { - SafeApiBuilder builder = new SafeApiBuilder(lookup); - AndroidApiLevelDatabaseTestHelper.visitAdditionalKnownApiReferences( - (methodReference, apiLevel) -> { - try { - assertTrue( - methodReference + " was expected present in android.jar", - jarInfoForVerification.hasMethod(methodReference)); - } catch (ApiDatabaseGeneratorException e) { - throw new RuntimeException(e); - } - builder.addMethod(methodReference, new ApiRange(apiLevel)); - }); - return builder.build(); - } - - /** - * These entries exist at runtime but not in android.jar or api-versions.xml. - * - * <p>The API entries are returned while the jar info is added to {@code jarInfo}. - */ - private static Collection<ParsedApiClass> addHiddenEntries( - Map<ClassReference, ApiRange> lookup, ApiJarInfo jarInfo) throws IOException { - SafeApiBuilder builder = new SafeApiBuilder(lookup); - - // This class is not present in .xml so it is skipped. - Map<String, Boolean> skipClasses = new HashMap<>(); - skipClasses.put("java/nio/DirectByteBuffer", false); - - AndroidApiLevelDatabaseTestHelper.visitHiddenReferences( - (classReference, superReference, interfaces, apiLevel) -> { - builder.addClass(classReference, new ApiRange(apiLevel)); - assertFalse( - classReference + " already exists in the JAR", jarInfo.hasClass(classReference)); - String superName; - if (superReference != null) { - superName = superReference.getBinaryName(); - } else if (interfaces.isEmpty()) { - superName = "java/lang/Object"; - } else { - superName = null; - } - boolean isInterface = superReference == null; - jarInfo.addClass( - new ApiClassInfo( - classReference.getBinaryName(), - superName, - ListUtils.map(interfaces, ClassReference::getBinaryName), - isInterface, - ImmutableList.of(), - ImmutableList.of())); - }, - (methodReference, isStatic, apiLevel) -> { - if (skipClasses.containsKey(methodReference.getHolderClass().getBinaryName())) { - skipClasses.put(methodReference.getHolderClass().getBinaryName(), true); - } else { - builder.addMethod(methodReference, new ApiRange(apiLevel)); - ApiClassInfo holder = jarInfo.getClassInfo(methodReference.getHolderClass()); - assertNotNull( - methodReference + " could not be added, holder not present in JAR", holder); - holder.addMethod( - methodReference.getMethodName(), methodReference.getMethodDescriptor(), isStatic); - } - }, - (fieldReference, apilevel) -> { - if (skipClasses.containsKey(fieldReference.getHolderClass().getBinaryName())) { - skipClasses.put(fieldReference.getHolderClass().getBinaryName(), true); - } else { - builder.addField(fieldReference, new ApiRange(apilevel)); - ApiClassInfo holder = jarInfo.getClassInfo(fieldReference.getHolderClass()); - assertNotNull( - fieldReference + " could not be added, holder not present in JAR", holder); - holder.addField(fieldReference.getFieldName()); - } - }); - for (Map.Entry<String, Boolean> entry : skipClasses.entrySet()) { - assertTrue(entry.getKey() + " was never skipped", entry.getValue()); - } - return builder.build(); - } - - private static Map<ClassReference, ApiRange> makeApiLookup( - Collection<ParsedApiClass> apiClasses) { - Map<ClassReference, ApiRange> lookup = new HashMap<>(); - for (ParsedApiClass apiClass : apiClasses) { - assertFalse( - apiClass.getClassReference() + " already found", - lookup.containsKey(apiClass.getClassReference())); - lookup.put(apiClass.getClassReference(), apiClass.getRange()); - } - return lookup; + return new ListeningDecorator<>(jarTrimmer, listener); } @SuppressWarnings("SameParameterValue") @@ -510,77 +310,6 @@ assertThat(missingMemberInformation.keySet(), matchesItemsOneToOne(expectedMissingMembers)); } - private static class SafeApiBuilder { - private final Map<ClassReference, ParsedApiClass> classes = new LinkedHashMap<>(); - private final Map<ClassReference, ApiRange> lookup; - - public SafeApiBuilder(Map<ClassReference, ApiRange> lookup) { - this.lookup = lookup; - } - - private ParsedApiClass getOrCreateClass(ClassReference classReference) { - ParsedApiClass apiClass = classes.get(classReference); - if (apiClass == null) { - ApiRange lookupRange = lookup.get(classReference); - assertNotNull(classReference + " not found in the lookup map", lookupRange); - ParsedApiClass baseApiClass = new ParsedApiClass(classReference, lookupRange); - classes.put(classReference, baseApiClass); - apiClass = baseApiClass; - } - return apiClass; - } - - public void addClass(ClassReference classReference, ApiRange apiRange) { - assertFalse(classReference + " was already found", classes.containsKey(classReference)); - classes.put(classReference, new ParsedApiClass(classReference, apiRange)); - } - - public void addMethod(MethodReference methodReference, ApiRange apiRange) { - ParsedApiClass apiClass = getOrCreateClass(methodReference.getHolderClass()); - assertFalse(methodReference + " was already found", apiClass.hasMethod(methodReference)); - AndroidApiLevel apiClassIntro = apiClass.getRange().intro; - AndroidApiLevel apiClassRemoved = apiClass.getRange().removed; - assertTrue( - "class intro " + apiClassIntro + " must be less than method intro " + apiRange.intro, - apiClassIntro.isLessThanOrEqualTo(apiRange.intro)); - assertTrue( - "class removed " - + apiClassRemoved - + " must be greater than method removed " - + apiRange.removed, - apiClassRemoved == null - || apiRange.isRemoved() - || apiClassRemoved.isGreaterThanOrEqualTo(apiRange.removed)); - apiClass.registerMethod(methodReference, apiRange); - } - - public void addField(FieldReference fieldReference, ApiRange apiRange) { - FieldTypelessReference field = - new FieldTypelessReference( - fieldReference.getHolderClass(), fieldReference.getFieldName()); - ParsedApiClass apiClass = getOrCreateClass(field.getHolderClass()); - assertFalse(field + " was already found", apiClass.hasField(field)); - AndroidApiLevel apiClassIntro = apiClass.getRange().intro; - AndroidApiLevel apiClassRemoved = apiClass.getRange().removed; - assertTrue( - "class intro " + apiClassIntro + " must be less than field intro " + apiRange.intro, - apiClassIntro.isLessThanOrEqualTo(apiRange.intro)); - assertTrue( - "class removed " - + apiClassRemoved - + " must be greater than field removed " - + apiRange.removed, - apiClassRemoved == null - || apiRange.isRemoved() - || apiClassRemoved.isGreaterThanOrEqualTo(apiRange.removed)); - apiClass.registerField(field, apiRange); - } - - public Collection<ParsedApiClass> build() { - return classes.values(); - } - } - @Test public void testEntrySize() throws Exception { Map<ApiDatabaseEntry, AndroidApiLevel> databaseEntries = computeEntries(loadParsedApiClasses()); @@ -590,9 +319,16 @@ /** If this test fails, use {@link #testDumpDatabase} to diff the content in a readable format. */ @Test public void testDatabaseGenerationUpToDate() throws Exception { - GenerateDatabaseResourceFilesResult result = - generateResourcesFiles(computeEntries(loadParsedApiClasses())); - assertTrue(TestBase.filesAreEqual(result.apiLevels, API_DATABASE)); + temp.create(); + Path apiLevels = temp.newFile("api_levels.ser").toPath(); + ApiDatabaseGeneratorCommand command = + ApiDatabaseGeneratorCommand.builder() + .addXmlPath(ToolHelper.getApiVersionsXmlFile(API_LEVEL)) + .addJarPath(ToolHelper.getAndroidJar(API_LEVEL)) + .setOutputPath(apiLevels) + .build(); + ApiDatabaseGenerator.run(command); + assertTrue(TestBase.filesAreEqual(apiLevels, API_DATABASE)); } @Test @@ -854,10 +590,24 @@ * third_party/api_database/api_database.ser. */ public static void main(String[] args) throws Exception { - GenerateDatabaseResourceFilesResult result = - generateResourcesFiles(computeEntries(loadParsedApiClasses())); - API_DATABASE.toFile().mkdirs(); - Files.move(result.apiLevels, API_DATABASE, REPLACE_EXISTING); + // ----------------------------------------- IMPORTANT ----------------------------------------- + // If the database format or content is changed DATABASE_FORMAT_VERSION must be incremented to + // reflect that the output of ApiDatabaseGenerator has changed. + // + // Updating api-versions.xml or android.jar does NOT need a version increase. + // ----------------------------------------- IMPORTANT ----------------------------------------- + TemporaryFolder temp = new TemporaryFolder(); + temp.create(); + Path apiLevels = temp.newFile("api_levels.ser").toPath(); + ApiDatabaseGeneratorCommand command = + ApiDatabaseGeneratorCommand.builder() + .addXmlPath(ToolHelper.getApiVersionsXmlFile(API_LEVEL)) + .addJarPath(ToolHelper.getAndroidJar(API_LEVEL)) + .setOutputPath(apiLevels) + .build(); + ApiDatabaseGenerator.run(command); + API_DATABASE.getParent().toFile().mkdirs(); + Files.move(apiLevels, API_DATABASE, REPLACE_EXISTING); System.out.println( "Updated file in: " + API_DATABASE
diff --git a/src/test/java8/apimodel/com/android/tools/r8/apimodel/ApiJarReaderTest.java b/src/test/java8/apimodel/com/android/tools/r8/apimodel/ApiJarReaderTest.java index a0388fd..1c6ee68 100644 --- a/src/test/java8/apimodel/com/android/tools/r8/apimodel/ApiJarReaderTest.java +++ b/src/test/java8/apimodel/com/android/tools/r8/apimodel/ApiJarReaderTest.java
@@ -14,7 +14,6 @@ import com.android.tools.r8.apimodel.jar.ApiJarInfo; import com.android.tools.r8.apimodel.jar.ApiJarReader; import com.android.tools.r8.utils.AndroidApiLevel; -import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -35,7 +34,7 @@ @Test public void testApiJarReader() throws Exception { AndroidApiLevel apiLevel = AndroidApiLevel.API_DATABASE_LEVEL; - ApiJarInfo jarInfo = ApiJarReader.read(ImmutableList.of(ToolHelper.getAndroidJar(apiLevel))); + ApiJarInfo jarInfo = ApiJarReader.read(ToolHelper.getAndroidJar(apiLevel)); assertNotNull(jarInfo); assertEquals(6_440, jarInfo.size()); assertTrue(jarInfo.hasClass("java/lang/Object"));
diff --git a/src/test/java8/apimodel/com/android/tools/r8/apimodel/ParsedApiClassJarTrimmerTest.java b/src/test/java8/apimodel/com/android/tools/r8/apimodel/ParsedApiClassJarTrimmerTest.java index b98a8cb..12d33bc 100644 --- a/src/test/java8/apimodel/com/android/tools/r8/apimodel/ParsedApiClassJarTrimmerTest.java +++ b/src/test/java8/apimodel/com/android/tools/r8/apimodel/ParsedApiClassJarTrimmerTest.java
@@ -12,11 +12,14 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.apimodel.ParsedApiClassTrimming.JarTrimmer; +import com.android.tools.r8.apimodel.jar.ApiJarInfo; +import com.android.tools.r8.apimodel.jar.ApiJarMerging; import com.android.tools.r8.apimodel.jar.ApiJarReader; import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.references.MethodReference; import com.android.tools.r8.references.Reference; import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.internal.ListUtils; import java.nio.file.Path; import java.util.Arrays; import java.util.Collection; @@ -144,8 +147,10 @@ Arrays.asList(apiClassA, apiClassB, apiInterfaceI, apiClassC, apiClassD); // Run JAR trimmer. + List<ApiJarInfo> readJars = ListUtils.mapThrowing(jars, ApiJarReader::read); + ApiJarInfo jarInfo = ApiJarMerging.mergeJarInfos(readJars); Collection<ParsedApiClass> trimmed = - ParsedApiClassTrimming.trim(apiClasses, new JarTrimmer(ApiJarReader.read(jars))); + ParsedApiClassTrimming.trim(apiClasses, new JarTrimmer(jarInfo)); // Verify results. Map<ClassReference, ParsedApiClass> trimmedMap = new HashMap<>();
diff --git a/src/test/java8/desugar/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodListCommandTest.java b/src/test/java8/desugar/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodListCommandTest.java index 3d22aaf..9487344 100644 --- a/src/test/java8/desugar/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodListCommandTest.java +++ b/src/test/java8/desugar/com/android/tools/r8/desugar/desugaredlibrary/DesugaredMethodListCommandTest.java
@@ -102,7 +102,7 @@ new String[] {"--desugared-lib"}, new Reporter(diagnosticMessages)); diagnosticMessages.assertOnlyErrors(); diagnosticMessages.assertErrorMessageThatMatches( - containsString("Missing parameter for --desugared-lib")); + containsString("Missing argument for --desugared-lib")); } @Test
diff --git a/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/ArraysMethods.java b/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/ArraysMethods.java index b56a1b1..46cd4a4 100644 --- a/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/ArraysMethods.java +++ b/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/ArraysMethods.java
@@ -4,6 +4,12 @@ package com.android.tools.r8.ir.desugar.backports; +import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.ArraysStub; +import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.ByteStub; +import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.ShortStub; +import java.util.Comparator; +import java.util.Objects; + public final class ArraysMethods { public static void checkValidRange(int arrayLength, int fromIndex, int toIndex) { @@ -304,4 +310,386 @@ } return true; } + + public static int compareUnsignedByte(byte[] a, byte[] b) { + if (a == b) { + return 0; + } + if (a == null) { + return -1; + } + if (b == null) { + return 1; + } + int i = ArraysStub.mismatch(a, b); + if (i >= 0 && i < Math.min(a.length, b.length)) { + return ByteStub.compareUnsigned(a[i], b[i]); + } + return a.length - b.length; + } + + public static int compareUnsignedByteRange( + byte[] a, int aFromIndex, int aToIndex, byte[] b, int bFromIndex, int bToIndex) { + int i = ArraysStub.mismatch(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex); + if (i >= 0 && i < Math.min(aToIndex - aFromIndex, bToIndex - bFromIndex)) { + return ByteStub.compareUnsigned(a[aFromIndex + i], b[bFromIndex + i]); + } + return (aToIndex - aFromIndex) - (bToIndex - bFromIndex); + } + + public static int compareUnsignedShort(short[] a, short[] b) { + if (a == b) { + return 0; + } + if (a == null) { + return -1; + } + if (b == null) { + return 1; + } + int i = ArraysStub.mismatch(a, b); + if (i >= 0 && i < Math.min(a.length, b.length)) { + return ShortStub.compareUnsigned(a[i], b[i]); + } + return a.length - b.length; + } + + public static int compareUnsignedShortRange( + short[] a, int aFromIndex, int aToIndex, short[] b, int bFromIndex, int bToIndex) { + int i = ArraysStub.mismatch(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex); + if (i >= 0 && i < Math.min(aToIndex - aFromIndex, bToIndex - bFromIndex)) { + return ShortStub.compareUnsigned(a[aFromIndex + i], b[bFromIndex + i]); + } + return (aToIndex - aFromIndex) - (bToIndex - bFromIndex); + } + + public static int compareUnsignedInt(int[] a, int[] b) { + if (a == b) { + return 0; + } + if (a == null) { + return -1; + } + if (b == null) { + return 1; + } + int i = ArraysStub.mismatch(a, b); + if (i >= 0 && i < Math.min(a.length, b.length)) { + return Integer.compareUnsigned(a[i], b[i]); + } + return a.length - b.length; + } + + public static int compareUnsignedIntRange( + int[] a, int aFromIndex, int aToIndex, int[] b, int bFromIndex, int bToIndex) { + int i = ArraysStub.mismatch(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex); + if (i >= 0 && i < Math.min(aToIndex - aFromIndex, bToIndex - bFromIndex)) { + return Integer.compareUnsigned(a[aFromIndex + i], b[bFromIndex + i]); + } + return (aToIndex - aFromIndex) - (bToIndex - bFromIndex); + } + + public static int compareUnsignedLong(long[] a, long[] b) { + if (a == b) { + return 0; + } + if (a == null) { + return -1; + } + if (b == null) { + return 1; + } + int i = ArraysStub.mismatch(a, b); + if (i >= 0 && i < Math.min(a.length, b.length)) { + return Long.compareUnsigned(a[i], b[i]); + } + return a.length - b.length; + } + + public static int compareUnsignedLongRange( + long[] a, int aFromIndex, int aToIndex, long[] b, int bFromIndex, int bToIndex) { + int i = ArraysStub.mismatch(a, aFromIndex, aToIndex, b, bFromIndex, bToIndex); + if (i >= 0 && i < Math.min(aToIndex - aFromIndex, bToIndex - bFromIndex)) { + return Long.compareUnsigned(a[aFromIndex + i], b[bFromIndex + i]); + } + return (aToIndex - aFromIndex) - (bToIndex - bFromIndex); + } + + public static int mismatchBoolean(boolean[] a, boolean[] b) { + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static int mismatchBooleanRange( + boolean[] a, int aFrom, int aTo, boolean[] b, int bFrom, int bTo) { + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (a[aFrom + i] != b[bFrom + i]) { + return i; + } + } + return aLength != bLength ? length : -1; + } + + public static int mismatchByte(byte[] a, byte[] b) { + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static int mismatchByteRange(byte[] a, int aFrom, int aTo, byte[] b, int bFrom, int bTo) { + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (a[aFrom + i] != b[bFrom + i]) { + return i; + } + } + return aLength != bLength ? length : -1; + } + + public static int mismatchChar(char[] a, char[] b) { + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static int mismatchCharRange(char[] a, int aFrom, int aTo, char[] b, int bFrom, int bTo) { + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (a[aFrom + i] != b[bFrom + i]) { + return i; + } + } + return aLength != bLength ? length : -1; + } + + public static int mismatchShort(short[] a, short[] b) { + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static int mismatchShortRange( + short[] a, int aFrom, int aTo, short[] b, int bFrom, int bTo) { + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (a[aFrom + i] != b[bFrom + i]) { + return i; + } + } + return aLength != bLength ? length : -1; + } + + public static int mismatchInt(int[] a, int[] b) { + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static int mismatchIntRange(int[] a, int aFrom, int aTo, int[] b, int bFrom, int bTo) { + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (a[aFrom + i] != b[bFrom + i]) { + return i; + } + } + return aLength != bLength ? length : -1; + } + + public static int mismatchLong(long[] a, long[] b) { + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static int mismatchLongRange(long[] a, int aFrom, int aTo, long[] b, int bFrom, int bTo) { + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (a[aFrom + i] != b[bFrom + i]) { + return i; + } + } + return aLength != bLength ? length : -1; + } + + public static int mismatchFloat(float[] a, float[] b) { + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (Float.floatToIntBits(a[i]) != Float.floatToIntBits(b[i])) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static int mismatchFloatRange( + float[] a, int aFrom, int aTo, float[] b, int bFrom, int bTo) { + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (Float.floatToIntBits(a[aFrom + i]) != Float.floatToIntBits(b[bFrom + i])) { + return i; + } + } + return aLength != bLength ? length : -1; + } + + public static int mismatchDouble(double[] a, double[] b) { + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (Double.doubleToLongBits(a[i]) != Double.doubleToLongBits(b[i])) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static int mismatchDoubleRange( + double[] a, int aFrom, int aTo, double[] b, int bFrom, int bTo) { + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (Double.doubleToLongBits(a[aFrom + i]) != Double.doubleToLongBits(b[bFrom + i])) { + return i; + } + } + return aLength != bLength ? length : -1; + } + + public static int mismatchObject(Object[] a, Object[] b) { + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (!Objects.equals(a[i], b[i])) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static int mismatchObjectRange( + Object[] a, int aFrom, int aTo, Object[] b, int bFrom, int bTo) { + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (!Objects.equals(a[aFrom + i], b[bFrom + i])) { + return i; + } + } + return aLength != bLength ? length : -1; + } + + public static <T> int mismatchComparator(T[] a, T[] b, Comparator<? super T> cmp) { + Objects.requireNonNull(cmp); + int length = Math.min(a.length, b.length); + if (a == b) { + return -1; + } + for (int i = 0; i < length; i++) { + if (cmp.compare(a[i], b[i]) != 0) { + return i; + } + } + return a.length != b.length ? length : -1; + } + + public static <T> int mismatchComparatorRange( + T[] a, int aFrom, int aTo, T[] b, int bFrom, int bTo, Comparator<? super T> cmp) { + Objects.requireNonNull(cmp); + checkValidRange(a.length, aFrom, aTo); + checkValidRange(b.length, bFrom, bTo); + int aLength = aTo - aFrom; + int bLength = bTo - bFrom; + int length = Math.min(aLength, bLength); + for (int i = 0; i < length; i++) { + if (cmp.compare(a[aFrom + i], b[bFrom + i]) != 0) { + return i; + } + } + return aLength != bLength ? length : -1; + } }
diff --git a/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/BackportMethodsStub.java b/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/BackportMethodsStub.java index 41e8b6d..775e192 100644 --- a/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/BackportMethodsStub.java +++ b/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/BackportMethodsStub.java
@@ -43,6 +43,56 @@ } } + static class ByteStub { + public static int compareUnsigned(byte x, byte y) { + return 0; + } + } + + static class ShortStub { + public static int compareUnsigned(short x, short y) { + return 0; + } + } + + static class ArraysStub { + public static int mismatch(byte[] a, byte[] b) { + return 0; + } + + public static int mismatch( + byte[] a, int aFromIndex, int aToIndex, byte[] b, int bFromIndex, int bToIndex) { + return 0; + } + + public static int mismatch(short[] a, short[] b) { + return 0; + } + + public static int mismatch( + short[] a, int aFromIndex, int aToIndex, short[] b, int bFromIndex, int bToIndex) { + return 0; + } + + public static int mismatch(int[] a, int[] b) { + return 0; + } + + public static int mismatch( + int[] a, int aFromIndex, int aToIndex, int[] b, int bFromIndex, int bToIndex) { + return 0; + } + + public static int mismatch(long[] a, long[] b) { + return 0; + } + + public static int mismatch( + long[] a, int aFromIndex, int aToIndex, long[] b, int bFromIndex, int bToIndex) { + return 0; + } + } + static class LongStub { public static long parseUnsignedLong(CharSequence s, int beginIndex, int endIndex, int radix) throws NumberFormatException {
diff --git a/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java index 8f10a52..e9c7de6 100644 --- a/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java +++ b/src/test/java8/ir/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -17,9 +17,12 @@ import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.AndroidOsBuildStub; import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.AndroidOsBuildVersionStub; +import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.ArraysStub; +import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.ByteStub; import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.IOStub; import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.LongStub; import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.MathStub; +import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.ShortStub; import com.android.tools.r8.ir.desugar.backports.BackportMethodsStub.UnsafeStub; import com.android.tools.r8.utils.DescriptorUtils; import com.google.common.collect.ImmutableList; @@ -110,19 +113,17 @@ @Override protected String generateMethods() throws IOException { stubMap = - ImmutableMap.of( - classToType(IOStub.class), - factory.createType("Ljava/lang/IO;"), - classToType(LongStub.class), - factory.boxedLongType, - classToType(MathStub.class), - factory.mathType, - classToType(UnsafeStub.class), - factory.sunMiscUnsafeType, - classToType(AndroidOsBuildStub.class), - factory.androidOsBuildType, - classToType(AndroidOsBuildVersionStub.class), - factory.androidOsBuildVersionType); + ImmutableMap.<DexType, DexType>builder() + .put(classToType(IOStub.class), factory.createType("Ljava/lang/IO;")) + .put(classToType(LongStub.class), factory.boxedLongType) + .put(classToType(ByteStub.class), factory.boxedByteType) + .put(classToType(ShortStub.class), factory.boxedShortType) + .put(classToType(ArraysStub.class), factory.arraysType) + .put(classToType(MathStub.class), factory.mathType) + .put(classToType(UnsafeStub.class), factory.sunMiscUnsafeType) + .put(classToType(AndroidOsBuildStub.class), factory.androidOsBuildType) + .put(classToType(AndroidOsBuildVersionStub.class), factory.androidOsBuildVersionType) + .build(); return super.generateMethods(); }
diff --git a/src/test/java8/retrace/com/android/tools/r8/retrace/HorizontalMergingWithMergedClassesMappingTest.java b/src/test/java8/retrace/com/android/tools/r8/retrace/HorizontalMergingWithMergedClassesMappingTest.java index 6133cc7..993b65d 100644 --- a/src/test/java8/retrace/com/android/tools/r8/retrace/HorizontalMergingWithMergedClassesMappingTest.java +++ b/src/test/java8/retrace/com/android/tools/r8/retrace/HorizontalMergingWithMergedClassesMappingTest.java
@@ -110,11 +110,11 @@ StringUtils.joinLines( "com.android.tools.r8.retrace.classes.SynthesizeLineNumber$A -> a:", "# {\"id\":\"com.android.tools.r8.mergedClasses\", \"kind\": \"horizontal\"," - + " \"class_id_field\": \"$r8$classId\", \"merged_classes\": [{ \"name\":" + + " \"classIdField\": \"$r8$classId\", \"mergedClasses\": [{ \"name\":" + " \"com.android.tools.r8.retrace.classes.SynthesizeLineNumber$A\"," - + " \"class_id\": \"0\" }, { \"name\":" + + " \"classId\": \"0\" }, { \"name\":" + " \"com.android.tools.r8.retrace.classes.SynthesizeLineNumber$B\"," - + " \"class_id\": \"1\" }] }")); + + " \"classId\": \"1\" }] }")); compilationResult .run(parameters.getRuntime(), SynthesizeLineNumber.Main.class, "normal") .inspectOriginalStackTrace(
diff --git a/src/utils/java/com/android/tools/r8/utils/internal/CliParser.java b/src/utils/java/com/android/tools/r8/utils/internal/CliParser.java index 04bc2ca..4d15b3c 100644 --- a/src/utils/java/com/android/tools/r8/utils/internal/CliParser.java +++ b/src/utils/java/com/android/tools/r8/utils/internal/CliParser.java
@@ -72,7 +72,7 @@ /** * @param name must start with {@code --} and must be unique and non-overlapping - * @param description must not contains line breaks, must end with {@code .}, and is automatically + * @param description must not contain line breaks, must end with {@code .}, and is automatically * wrapped */ public CliParser<B> option0(String name, String description, Consumer<B> action) { @@ -84,7 +84,7 @@ /** * @param name must start with {@code --} and must be unique and non-overlapping - * @param description must not contains line breaks, must end with {@code .}, and is automatically + * @param description must not contain line breaks, must end with {@code .}, and is automatically * wrapped * @param shorthand must start with {@code -} and must be unique and non-overlapping */ @@ -100,7 +100,7 @@ /** * @param name must start with {@code --} and must be unique and non-overlapping * @param paramLabel must be surrounded by {@code <} and {@code >} - * @param description must not contains line breaks, must end with {@code .}, and is automatically + * @param description must not contain line breaks, must end with {@code .}, and is automatically * wrapped */ public CliParser<B> option1( @@ -115,7 +115,7 @@ /** * @param name must start with {@code --} and must be unique and non-overlapping * @param paramLabel must be surrounded by {@code <} and {@code >} - * @param description must not contains line breaks, must end with {@code .}, and is automatically + * @param description must not contain line breaks, must end with {@code .}, and is automatically * wrapped * @param shorthand must start with {@code -} and must be unique and non-overlapping */ @@ -134,17 +134,61 @@ } /** + * @param name must start with {@code --} and must be unique and non-overlapping + * @param paramLabel1 must be surrounded by {@code <} and {@code >} + * @param paramLabel2 must be surrounded by {@code <} and {@code >} + * @param description must not contain line breaks, must end with {@code .}, and is automatically + * wrapped + */ + public CliParser<B> option2( + String name, + String paramLabel1, + String paramLabel2, + String description, + TriConsumer<B, String, String> action) { + checkOptionName(name); + checkParam(paramLabel1); + checkParam(paramLabel2); + checkDescription(description); + base.option2(name, paramLabel1, paramLabel2, description, action); + return this; + } + + /** + * @param name must start with {@code --} and must be unique and non-overlapping + * @param paramLabel1 must be surrounded by {@code <} and {@code >} + * @param paramLabel2 must be surrounded by {@code <} and {@code >} + * @param description must not contain line breaks, must end with {@code .}, and is automatically + * wrapped + * @param shorthand must start with {@code -} and must be unique and non-overlapping + */ + public CliParser<B> option2( + String name, + String paramLabel1, + String paramLabel2, + String description, + TriConsumer<B, String, String> action, + String shorthand) { + checkOptionName(name); + checkShorthand(shorthand); + checkParam(paramLabel1); + checkParam(paramLabel2); + checkDescription(description); + base.option2(name, paramLabel1, paramLabel2, description, action, shorthand); + return this; + } + + /** * It is assumed that the suffix is optional. * * @param prefix must start with {@code --} and must be unique and non-overlapping - * @param suffixLabel must be surrounded by {@code [} and {@code ]} - * @param description must not contains line breaks, must end with {@code .}, and is automatically + * @param description must not contain line breaks, must end with {@code .}, and is automatically * wrapped */ public CliParser<B> prefix0( String prefix, String suffixLabel, String description, BiConsumer<B, String> action) { checkOptionName(prefix); - checkOptionalParam(suffixLabel); + checkSuffixLabel(suffixLabel); checkDescription(description); base.prefix0(prefix, suffixLabel, description, action); return this; @@ -154,9 +198,31 @@ * It is assumed that the suffix is optional. * * @param prefix must start with {@code --} and must be unique and non-overlapping - * @param suffixLabel must be surrounded by {@code [} and {@code ]} + * @param description must not contain line breaks, must end with {@code .}, and is automatically + * wrapped + * @param shorthand must start with {@code -} or {@code --} and must be unique and non-overlapping + */ + public CliParser<B> prefix0( + String prefix, + String suffixLabel, + String description, + BiConsumer<B, String> action, + String shorthand) { + checkOptionName(prefix); + checkShorthand(shorthand); + checkSuffixLabel(suffixLabel); + checkDescription(description); + base.prefix0(prefix, suffixLabel, description, action, shorthand); + return this; + } + + /** + * It is assumed that the suffix is optional. + * + * @param prefix must start with {@code --} and must be unique and non-overlapping + * @param suffixLabel descriptive suffix label * @param paramLabel must be surrounded by {@code <} and {@code >} - * @param description must not contains line breaks, must end with {@code .}, and is automatically + * @param description must not contain line breaks, must end with {@code .}, and is automatically * wrapped */ public CliParser<B> prefix1( @@ -166,7 +232,7 @@ String description, TriConsumer<B, String, String> action) { checkOptionName(prefix); - checkOptionalParam(suffixLabel); + checkSuffixLabel(suffixLabel); checkParam(paramLabel); checkDescription(description); base.prefix1(prefix, suffixLabel, paramLabel, description, action); @@ -177,10 +243,35 @@ * It is assumed that the suffix is optional. * * @param prefix must start with {@code --} and must be unique and non-overlapping - * @param suffixLabel must be surrounded by {@code [} and {@code ]} + * @param paramLabel must be surrounded by {@code <} and {@code >} + * @param description must not contain line breaks, must end with {@code .}, and is automatically + * wrapped + * @param shorthand must start with {@code -} or {@code --} and must be unique and non-overlapping + */ + public CliParser<B> prefix1( + String prefix, + String suffixLabel, + String paramLabel, + String description, + TriConsumer<B, String, String> action, + String shorthand) { + checkOptionName(prefix); + checkShorthand(shorthand); + checkSuffixLabel(suffixLabel); + checkParam(paramLabel); + checkDescription(description); + base.prefix1(prefix, suffixLabel, paramLabel, description, action, shorthand); + return this; + } + + /** + * It is assumed that the suffix is optional. + * + * @param prefix must start with {@code --} and must be unique and non-overlapping + * @param suffixLabel descriptive suffix label * @param paramLabel1 must be surrounded by {@code <} and {@code >} * @param paramLabel2 must be surrounded by {@code <} and {@code >} - * @param description must not contains line breaks, must end with {@code .}, and is automatically + * @param description must not contain line breaks, must end with {@code .}, and is automatically * wrapped */ public CliParser<B> prefix2( @@ -191,7 +282,7 @@ String description, QuadConsumer<B, String, String, String> action) { checkOptionName(prefix); - checkOptionalParam(suffixLabel); + checkSuffixLabel(suffixLabel); checkParam(paramLabel1); checkParam(paramLabel2); checkDescription(description); @@ -199,6 +290,34 @@ return this; } + /** + * It is assumed that the suffix is optional. + * + * @param prefix must start with {@code --} and must be unique and non-overlapping + * @param paramLabel1 must be surrounded by {@code <} and {@code >} + * @param paramLabel2 must be surrounded by {@code <} and {@code >} + * @param description must not contain line breaks, must end with {@code .}, and is automatically + * wrapped + * @param shorthand must start with {@code -} or {@code --} and must be unique and non-overlapping + */ + public CliParser<B> prefix2( + String prefix, + String suffixLabel, + String paramLabel1, + String paramLabel2, + String description, + QuadConsumer<B, String, String, String> action, + String shorthand) { + checkOptionName(prefix); + checkShorthand(shorthand); + checkSuffixLabel(suffixLabel); + checkParam(paramLabel1); + checkParam(paramLabel2); + checkDescription(description); + base.prefix2(prefix, suffixLabel, paramLabel1, paramLabel2, description, action, shorthand); + return this; + } + private void checkOptionName(String name) { assert name.startsWith("--") : name + " does not start with --"; assert name.length() > 2 : name + " is an empty name"; @@ -207,8 +326,11 @@ private void checkShorthand(String shorthand) { assert shorthand.startsWith("-") : shorthand + " does not start with -"; - assert shorthand.length() > 1 : shorthand + " is an empty name"; - assert shorthand.charAt(1) != '-' : shorthand + " has a second '-'"; + if (shorthand.startsWith("--")) { + checkOptionName(shorthand); + } else { + assert shorthand.length() > 1 : shorthand + " is an empty name"; + } } private void checkParam(String param) { @@ -216,9 +338,8 @@ assert param.length() > 2 : "parameter label content is empty: " + param; } - private void checkOptionalParam(String param) { - assert param.startsWith("[") && param.endsWith("]") : param + " is not surrounded by []."; - assert param.length() > 2 : "optional parameter label content is empty: " + param; + private void checkSuffixLabel(String suffixLabel) { + assert !suffixLabel.isEmpty() : "suffix label is empty."; } private void checkDescription(String description) {
diff --git a/src/utils/java/com/android/tools/r8/utils/internal/CliParserBase.java b/src/utils/java/com/android/tools/r8/utils/internal/CliParserBase.java index 2fa4b5d..1d7265c 100644 --- a/src/utils/java/com/android/tools/r8/utils/internal/CliParserBase.java +++ b/src/utils/java/com/android/tools/r8/utils/internal/CliParserBase.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.utils.internal; import com.android.tools.r8.utils.internal.collections.Pair; +import com.android.tools.r8.utils.internal.exceptions.Unreachable; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Deque; @@ -17,6 +18,7 @@ private final Map<String, Consumer<B>> options0 = new HashMap<>(); private final Map<String, BiConsumer<B, String>> options1 = new HashMap<>(); + private final Map<String, TriConsumer<B, String, String>> options2 = new HashMap<>(); private final Map<String, BiConsumer<B, String>> prefix0 = new HashMap<>(); private final Map<String, TriConsumer<B, String, String>> prefix1 = new HashMap<>(); private final Map<String, QuadConsumer<B, String, String, String>> prefix2 = new HashMap<>(); @@ -104,6 +106,37 @@ } /** + * @param name must be unique and non-overlapping + */ + public CliParserBase<B> option2( + String name, + String paramLabel1, + String paramLabel2, + String description, + TriConsumer<B, String, String> action) { + addOption2(name, action); + addHelp(name, null, null, ImmutableList.of(paramLabel1, paramLabel2), description); + return this; + } + + /** + * @param name must be unique and non-overlapping + * @param shorthand must be unique and non-overlapping + */ + public CliParserBase<B> option2( + String name, + String paramLabel1, + String paramLabel2, + String description, + TriConsumer<B, String, String> action, + String shorthand) { + addOption2(name, action); + addOption2(shorthand, action); + addHelp(name, shorthand, null, ImmutableList.of(paramLabel1, paramLabel2), description); + return this; + } + + /** * @param prefix must start with {@code --} and must be unique and non-overlapping */ public CliParserBase<B> prefix0( @@ -115,6 +148,22 @@ /** * @param prefix must start with {@code --} and must be unique and non-overlapping + * @param shorthand must be unique and non-overlapping + */ + public CliParserBase<B> prefix0( + String prefix, + String suffixLabel, + String description, + BiConsumer<B, String> action, + String shorthand) { + addPrefix0(prefix, action); + addPrefix0(shorthand, action); + addHelp(prefix, shorthand, suffixLabel, ImmutableList.of(), description); + return this; + } + + /** + * @param prefix must start with {@code --} and must be unique and non-overlapping */ public CliParserBase<B> prefix1( String prefix, @@ -129,6 +178,23 @@ /** * @param prefix must start with {@code --} and must be unique and non-overlapping + * @param shorthand must be unique and non-overlapping + */ + public CliParserBase<B> prefix1( + String prefix, + String suffixLabel, + String paramLabel, + String description, + TriConsumer<B, String, String> action, + String shorthand) { + addPrefix1(prefix, action); + addPrefix1(shorthand, action); + addHelp(prefix, shorthand, suffixLabel, ImmutableList.of(paramLabel), description); + return this; + } + + /** + * @param prefix must start with {@code --} and must be unique and non-overlapping */ public CliParserBase<B> prefix2( String prefix, @@ -143,6 +209,25 @@ } /** + * @param prefix must start with {@code --} and must be unique and non-overlapping + * @param shorthand must be unique and non-overlapping + */ + public CliParserBase<B> prefix2( + String prefix, + String suffixLabel, + String paramLabel1, + String paramLabel2, + String description, + QuadConsumer<B, String, String, String> action, + String shorthand) { + addPrefix2(prefix, action); + addPrefix2(shorthand, action); + addHelp( + prefix, shorthand, suffixLabel, ImmutableList.of(paramLabel1, paramLabel2), description); + return this; + } + + /** * @param action only one positional handler can be bound */ public CliParserBase<B> positional(BiConsumer<B, String> action) { @@ -178,16 +263,32 @@ } } + Pair<String, String> prefixMatch; if (tryParseOption0(arg, eqValue, builder, errorReporter)) { // Matched. } else if (tryParseOption1(arg, eqValue, args, builder, errorReporter)) { // Matched. - } else if (tryParsePrefix0(arg, eqValue, builder, errorReporter)) { + } else if (tryParseOption2(arg, eqValue, args, builder, errorReporter)) { // Matched. - } else if (tryParsePrefix1(arg, eqValue, args, builder, errorReporter)) { - // Matched. - } else if (tryParsePrefix2(arg, eqValue, args, builder, errorReporter)) { - // Matched. + } else if ((prefixMatch = findLongestMatchingPrefix(arg)) != null) { + String prefix = prefixMatch.getFirst(); + String suffix = prefixMatch.getSecond(); + if (tryParsePrefix0(prefix, suffix, eqValue, builder, errorReporter)) { + // Matched. + } else if (tryParsePrefix1(prefix, suffix, eqValue, args, builder, errorReporter)) { + // Matched. + } else if (tryParsePrefix2(prefix, suffix, eqValue, args, builder, errorReporter)) { + // Matched. + } else { + throw new Unreachable( + "The found prefix " + + prefix + + " of " + + arg + + " was not handled (suffix: " + + suffix + + ")"); + } } else if (tryParsePositional(rawArg, builder)) { // Matched. } else { @@ -219,71 +320,122 @@ } else if (!args.isEmpty()) { options1.get(arg).accept(builder, args.removeFirst()); } else { - errorReporter.accept("Missing parameter for " + arg + "."); + errorReporter.accept("Missing argument for " + arg + "."); args.clear(); } return true; } - private boolean tryParsePrefix0( - String arg, String eqValue, B builder, Consumer<String> errorReporter) { - Pair<String, BiConsumer<B, String>> match = findMatchedPrefix(arg, prefix0); - if (match == null) { + private boolean tryParseOption2( + String arg, String eqValue, Deque<String> args, B builder, Consumer<String> errorReporter) { + if (!options2.containsKey(arg)) { return false; } if (eqValue != null) { - errorReporter.accept("Option " + arg + " does not take a value."); + errorReporter.accept( + "Cannot use '--option=argument' syntax for " + arg + " (expects 2 arguments)."); + args.clear(); + return true; + } + + String arg1; + if (!args.isEmpty()) { + arg1 = args.removeFirst(); } else { - match.getSecond().accept(builder, match.getFirst()); + errorReporter.accept("Missing arguments for " + arg + " (expects 2 arguments)."); + args.clear(); + return true; + } + + String arg2; + if (!args.isEmpty()) { + arg2 = args.removeFirst(); + } else { + errorReporter.accept("Missing second argument for " + arg + " (expects 2 arguments)."); + args.clear(); + return true; + } + + options2.get(arg).accept(builder, arg1, arg2); + return true; + } + + private boolean tryParsePrefix0( + String prefix, String suffix, String eqValue, B builder, Consumer<String> errorReporter) { + BiConsumer<B, String> handler = prefix0.get(prefix); + if (handler == null) { + return false; + } + if (eqValue != null) { + errorReporter.accept("Option " + prefix + suffix + " does not take an argument."); + } else { + handler.accept(builder, suffix); } return true; } private boolean tryParsePrefix1( - String arg, String eqValue, Deque<String> args, B builder, Consumer<String> errorReporter) { - Pair<String, TriConsumer<B, String, String>> match = findMatchedPrefix(arg, prefix1); - if (match == null) { + String prefix, + String suffix, + String eqValue, + Deque<String> args, + B builder, + Consumer<String> errorReporter) { + TriConsumer<B, String, String> handler = prefix1.get(prefix); + if (handler == null) { return false; } if (eqValue != null) { - match.getSecond().accept(builder, match.getFirst(), eqValue); + handler.accept(builder, suffix, eqValue); } else if (!args.isEmpty()) { - match.getSecond().accept(builder, match.getFirst(), args.removeFirst()); + handler.accept(builder, suffix, args.removeFirst()); } else { - errorReporter.accept("Missing parameter for " + arg + "."); + errorReporter.accept("Missing argument for " + prefix + suffix + "."); args.clear(); } return true; } private boolean tryParsePrefix2( - String arg, String eqValue, Deque<String> args, B builder, Consumer<String> errorReporter) { - Pair<String, QuadConsumer<B, String, String, String>> match = findMatchedPrefix(arg, prefix2); - if (match == null) { + String prefix, + String suffix, + String eqValue, + Deque<String> args, + B builder, + Consumer<String> errorReporter) { + QuadConsumer<B, String, String, String> handler = prefix2.get(prefix); + if (handler == null) { return false; } - String arg1; - String arg2; if (eqValue != null) { - arg1 = eqValue; - if (!args.isEmpty()) { - arg2 = args.removeFirst(); - } else { - errorReporter.accept("Missing parameter for " + arg + "."); - args.clear(); - return true; - } - } else { - if (args.size() >= 2) { - arg1 = args.removeFirst(); - arg2 = args.removeFirst(); - } else { - errorReporter.accept("Missing parameter for " + arg + "."); - args.clear(); - return true; - } + errorReporter.accept( + "Cannot use '--option=argument' syntax for " + + prefix + + suffix + + " (expects 2 arguments)."); + args.clear(); + return true; } - match.getSecond().accept(builder, match.getFirst(), arg1, arg2); + + String arg1; + if (!args.isEmpty()) { + arg1 = args.removeFirst(); + } else { + errorReporter.accept("Missing arguments for " + prefix + suffix + " (expects 2 arguments)."); + args.clear(); + return true; + } + + String arg2; + if (!args.isEmpty()) { + arg2 = args.removeFirst(); + } else { + errorReporter.accept( + "Missing second argument for " + prefix + suffix + " (expects 2 arguments)."); + args.clear(); + return true; + } + handler.accept(builder, suffix, arg1, arg2); return true; } @@ -295,13 +447,22 @@ return true; } - /** The returned string in the pair is the suffix part of arg. */ - private <V> Pair<String, V> findMatchedPrefix(String arg, Map<String, V> prefixMap) { - for (String prefix : prefixMap.keySet()) { - if (arg.startsWith(prefix)) { - String suffix = arg.substring(prefix.length()); - return Pair.create(suffix, prefixMap.get(prefix)); - } + /** Returns (prefix, suffix) for the longest prefix match in {@link #forEachPrefix} if any. */ + private Pair<String, String> findLongestMatchingPrefix(String arg) { + final Box<String> bestMatchBox = new Box<>(null); + forEachPrefix( + prefix -> { + if (arg.startsWith(prefix)) { + String bestMatch = bestMatchBox.get(); + if (bestMatch == null || prefix.length() > bestMatch.length()) { + bestMatchBox.set(prefix); + } + } + }); + String bestMatch = bestMatchBox.get(); + if (bestMatch != null) { + String suffix = arg.substring(bestMatch.length()); + return Pair.create(bestMatch, suffix); } return null; } @@ -316,6 +477,11 @@ options1.put(name, action); } + private void addOption2(String name, TriConsumer<B, String, String> action) { + assert assertThatOptionIsNew(name); + options2.put(name, action); + } + private void addPrefix0(String prefix, BiConsumer<B, String> action) { assert assertThatPrefixIsNew(prefix); prefix0.put(prefix, action); @@ -348,6 +514,7 @@ private void forEachOption(Consumer<String> action) { options0.keySet().forEach(action); options1.keySet().forEach(action); + options2.keySet().forEach(action); } private void forEachPrefix(Consumer<String> action) { @@ -378,8 +545,7 @@ }); forEachPrefix( existing -> { - assert !name.startsWith(existing) && !existing.startsWith(name) - : "Overlap detected: Prefix " + name + " and prefix " + existing; + assert !name.equals(existing) : "Duplicate prefix: " + name; }); return true; }
diff --git a/src/utils/java/com/android/tools/r8/utils/internal/ListUtils.java b/src/utils/java/com/android/tools/r8/utils/internal/ListUtils.java index 4e23727..f801e99 100644 --- a/src/utils/java/com/android/tools/r8/utils/internal/ListUtils.java +++ b/src/utils/java/com/android/tools/r8/utils/internal/ListUtils.java
@@ -128,6 +128,15 @@ return result; } + public static <S, T, E extends Throwable> List<T> mapThrowing( + Collection<S> list, ThrowingFunction<S, T, E> fn) throws E { + List<T> result = new ArrayList<>(list.size()); + for (S element : list) { + result.add(fn.apply(element)); + } + return result; + } + public static <S, T> List<T> mapNotNull(Collection<S> list, Function<S, T> fn) { List<T> result = new ArrayList<>(list.size()); for (S element : list) {
diff --git a/src/utils/java/com/android/tools/r8/utils/internal/StringBuilderUtils.java b/src/utils/java/com/android/tools/r8/utils/internal/StringBuilderUtils.java new file mode 100644 index 0000000..34dae39 --- /dev/null +++ b/src/utils/java/com/android/tools/r8/utils/internal/StringBuilderUtils.java
@@ -0,0 +1,20 @@ +// Copyright (c) 2026, 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.internal; + +public class StringBuilderUtils { + + /** Append lines with platform-dependent newline characters. */ + public static StringBuilder appendLines(StringBuilder builder, Iterable<String> lines) { + lines.forEach(line -> appendLine(builder, line)); + return builder; + } + + /** Append line with platform-dependent newline characters. */ + public static StringBuilder appendLine(StringBuilder builder, String line) { + builder.append(line).append(System.lineSeparator()); + return builder; + } +}
diff --git a/third_party/api_database/api_database.tar.gz.sha1 b/third_party/api_database/api_database.tar.gz.sha1 index 3f41118..c6c0f0d 100644 --- a/third_party/api_database/api_database.tar.gz.sha1 +++ b/third_party/api_database/api_database.tar.gz.sha1
@@ -1 +1 @@ -8db44f4f89a9c2417571c9e9d5967d4930bff19a \ No newline at end of file +eb96f1f0c74274aedd318d9528da49b5583c3aa4 \ No newline at end of file
diff --git a/tools/archive_smali.py b/tools/archive_smali.py index 37a2ec7..753b8e4 100755 --- a/tools/archive_smali.py +++ b/tools/archive_smali.py
@@ -65,6 +65,8 @@ set_rlimit_to_max() utils.ensure_google_download(utils.JAVA11_DIR) + utils.ensure_google_download(utils.JAVA21_DIR) + bot_dir = os.getcwd() with utils.TempDir() as temp: # Resolve dry run location to support relative directories. dry_run_output = None @@ -102,17 +104,21 @@ print('Building version: %s' % version) + # Run tests with JDK-21. + custom_env = os.environ.copy() + custom_env["JAVA_HOME"] = jdk.GetJdk21Home() + subprocess.check_call(['./gradlew', 'test', ], env=custom_env) + # Build release to local Maven repository compiling with JDK-11. m2 = os.path.join(temp, 'm2') os.mkdir(m2) + custom_env["JAVA_HOME"] = jdk.GetJdk11Home() subprocess.check_call([ './gradlew', - '-Dorg.gradle.java.home=%s' % jdk.GetJdk11Home(), '-Dmaven.repo.local=%s' % m2, 'release', - 'test', 'publishToMavenLocal', - ]) + ], env=custom_env) base = os.path.join('com', 'android', 'tools', 'smali') # Check that the local maven repository only has the single version directory in @@ -181,7 +187,9 @@ public_url = 'https://storage.googleapis.com/%s/smali/%s' % ( ARCHIVE_BUCKET, version) # Copy Maven ZIP for LUCI recipie to find it. - shutil.copyfile(maven_release_archive, 'smali-maven-release.zip') + shutil.copyfile( + maven_release_archive, + os.path.join(bot_dir, 'smali-maven-release.zip')) print('Artifacts available at: %s' % public_url) print("Done!")
diff --git a/tools/create_art_tests.py b/tools/create_art_tests.py deleted file mode 100755 index 4d7c15b..0000000 --- a/tools/create_art_tests.py +++ /dev/null
@@ -1,112 +0,0 @@ -#!/usr/bin/env python3 -# 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. - -import os -import shutil -from string import Template - -OUTPUT_DIR = os.path.join('build', 'generated', 'test', 'java', 'com', - 'android', 'tools', 'r8', 'art') -TEST_DIR = os.path.join('tests', '2017-10-04', 'art') -TOOLCHAINS = [ - ("none", os.path.join(TEST_DIR, "dx")), -] -TOOLS = ["r8", "d8", "r8cf"] -TEMPLATE = Template( - """// 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 com.android.tools.r8.art.$testGeneratingToolchain.$compilerUnderTest; - -import static com.android.tools.r8.R8RunArtTestsTest.DexTool.$testGeneratingToolchainEnum; - -import com.android.tools.r8.R8RunArtTestsTest; -import com.android.tools.r8.TestBase; -import com.android.tools.r8.TestParameters; -import com.android.tools.r8.TestParametersCollection; -import com.android.tools.r8.ToolHelper.DexVm; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -/** - * Auto-generated test for the art $name test using the $testGeneratingToolchain toolchain. - * - * DO NOT EDIT THIS FILE. EDIT THE HERE DOCUMENT TEMPLATE IN tools/create_art_tests.py INSTEAD! - */ -@RunWith(Parameterized.class) -public class $testClassName extends R8RunArtTestsTest { - - @Parameters(name = "{0}") - public static TestParametersCollection data() { - return TestBase.getTestParameters().$testRuntimes.build(); - } - - private final TestParameters parameters; - - public $testClassName(TestParameters parameters) { - super("$name", $testGeneratingToolchainEnum); - this.parameters = parameters; - } - - @Test - public void test() throws Throwable { - runArtTest(parameters.getRuntime().asDex().getVm(), CompilerUnderTest.$compilerUnderTestEnum); - } -} -""") - - -def get_test_configurations(): - for toolchain, source_dir in TOOLCHAINS: - for tool in TOOLS: - if tool == "d8" and toolchain == "none": - tool_enum = 'R8_AFTER_D8' - else: - tool_enum = tool.upper() - if tool == "r8cf": - if toolchain != "none": - continue - tool_enum = 'D8_AFTER_R8CF' - output_dir = os.path.join(OUTPUT_DIR, toolchain, tool) - yield (tool_enum, tool, toolchain, source_dir, output_dir) - - -def create_tests(): - for tool_enum, tool, toolchain, source_dir, output_dir in get_test_configurations( - ): - test_cases = [ - d for d in os.listdir(source_dir) - if os.path.isdir(os.path.join(source_dir, d)) - ] - if os.path.exists(output_dir): - shutil.rmtree(output_dir) - os.makedirs(output_dir) - for test_case in test_cases: - class_name = "Art" + test_case.replace("-", "_") + "Test" - template = TEMPLATE - test_runtimes = "withDexRuntimes()" - if (test_case in ["800-smali"]): - test_runtimes = "withDexRuntimesEndingAtExcluding(\n com.android.tools.r8.ToolHelper.DexVm.Version.V17_0_0)" - contents = template.substitute( - testRuntimes=test_runtimes, - name=test_case, - compilerUnderTestEnum=tool_enum, - compilerUnderTest=tool, - testGeneratingToolchain=toolchain, - testGeneratingToolchainEnum=toolchain.upper(), - testClassName=class_name) - with open(os.path.join(output_dir, class_name + ".java"), - "w") as fp: - fp.write(contents) - - -def main(): - create_tests() - - -if __name__ == "__main__": - main()
diff --git a/tools/trigger.py b/tools/trigger.py index f07873a..aebb332 100755 --- a/tools/trigger.py +++ b/tools/trigger.py
@@ -56,7 +56,7 @@ help='Build smali version <version>.') result.add_option('--builder', help='Trigger specific builder') (options, args) = result.parse_args() - if not options.cl.startswith('http'): + if options.cl and not options.cl.startswith('http'): [cl_number, patch_number] = [int(x) for x in options.cl.split('/')] options.cl = 'https://r8-review.googlesource.com/c/r8/+/%s/%s' % ( cl_number, patch_number)