| // 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; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| import com.android.tools.r8.ToolHelper.DexVm; |
| import com.android.tools.r8.ToolHelper.DexVm.Version; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.TestDescriptionWatcher; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.ExpectedException; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @RunWith(Parameterized.class) |
| public class R8RunSmaliTestsTest extends TestBase { |
| |
| @Rule |
| public ExpectedException thrown = ExpectedException.none(); |
| |
| @Parameters(name = "{0}: {1}") |
| public static Collection<Object[]> data() { |
| return buildParameters( |
| getTestParameters().withDexRuntimes().withAllApiLevels().build(), tests.keySet()); |
| } |
| |
| private static final String SMALI_DIR = ToolHelper.SMALI_BUILD_DIR; |
| |
| private static Map<String, String> tests; |
| |
| static { |
| ImmutableMap.Builder<String, String> testsBuilder = ImmutableMap.builder(); |
| testsBuilder |
| .put( |
| "arithmetic", |
| StringUtils.lines( |
| "-1", "3", "2", "3", "3.0", "1", "0", "-131580", "-131580", "2", "4", "-2")) |
| .put( |
| "controlflow", |
| StringUtils.lines("2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2")) |
| .put("fibonacci", StringUtils.lines("55", "55", "55", "55")) |
| .put("fill-array-data", "[1, 2, 3][4, 5, 6]") |
| .put("filled-new-array", "[1, 2, 3][4, 5, 6][1, 2, 3, 4, 5, 6][6, 5, 4, 3, 2, 1]") |
| .put("packed-switch", "12345") |
| .put("sparse-switch", "12345") |
| .put("unreachable-code-1", "777") |
| .put( |
| "multiple-returns", |
| StringUtils.lines("TFtf", "1", "4611686018427387904", "true", "false")) |
| .put("try-catch", "") |
| .put("phi-removal-regression", StringUtils.lines("returnBoolean")) |
| .put( |
| "overlapping-long-registers", |
| StringUtils.lines("-9151314442816847872", "-9151314442816319488")) |
| .put( |
| "type-confusion-regression", |
| StringUtils.lines("java.lang.RuntimeException: Test.<init>()")) |
| .put( |
| "type-confusion-regression2", |
| StringUtils.lines("java.lang.NullPointerException: Attempt to read from null array")) |
| .put( |
| "type-confusion-regression3", |
| StringUtils.lines( |
| "java.lang.NullPointerException: Attempt to read from field 'byte[] Test.a'" |
| + " on a null object reference")) |
| .put("type-confusion-regression4", "") |
| .put( |
| "type-confusion-regression5", StringUtils.lines("java.lang.RuntimeException: getId()I")) |
| .put("chain-of-loops", StringUtils.lines("java.lang.RuntimeException: f(II)")) |
| .put("new-instance-and-init", StringUtils.lines("Test(0)", "Test(0)", "Test(0)")) |
| .put( |
| "bad-codegen", |
| StringUtils.lines( |
| "java.lang.NullPointerException: Attempt to read from field " |
| + "'Test Test.a' on a null object reference")) |
| .put( |
| "merge-blocks-regression", |
| StringUtils.lines( |
| "java.lang.NullPointerException: Attempt to invoke virtual" |
| + " method 'Test Test.bW_()' on a null object reference")) |
| .put("self-is-catch-block", StringUtils.lines("100", "-1")) |
| .put("infinite-loop", "") |
| .put( |
| "regression/33336471", |
| StringUtils.lines( |
| "START", "0", "2", "LOOP", "1", "2", "LOOP", "2", "2", "DONE", "START", "0", "2", |
| "LOOP", "1", "2", "LOOP", "2", "2", "DONE")) |
| .put("regression/33846227", "") |
| .put("illegal-invokes", StringUtils.lines("ICCE", "ICCE")) |
| .build(); |
| tests = testsBuilder.build(); |
| } |
| |
| private static Map<String, Set<String>> missingClasses = |
| ImmutableMap.of( |
| "try-catch", ImmutableSet.of("test.X"), |
| "type-confusion-regression5", ImmutableSet.of("jok", "jol"), |
| "bad-codegen", ImmutableSet.of("java.util.LTest")); |
| |
| // Tests where the original smali code fails on Art, but runs after R8 processing. |
| private static final Map<DexVm.Version, List<String>> originalFailingOnArtVersions = |
| ImmutableMap.of( |
| Version.V5_1_1, |
| ImmutableList.of( |
| // Smali code contains an empty switch payload. |
| "sparse-switch", "regression/33846227"), |
| Version.V4_4_4, |
| ImmutableList.of( |
| // Smali code contains an empty switch payload. |
| "sparse-switch", "regression/33846227"), |
| Version.V4_0_4, |
| ImmutableList.of( |
| // Smali code contains an empty switch payload. |
| "sparse-switch", "regression/33846227")); |
| |
| // Tests where the output has a different output than the original on certain VMs. |
| private static final Map<DexVm.Version, Map<String, String>> customProcessedOutputExpectation = |
| ImmutableMap.of( |
| Version.V4_4_4, |
| ImmutableMap.of( |
| "bad-codegen", "java.lang.NullPointerException\n", |
| "type-confusion-regression2", "java.lang.NullPointerException\n", |
| "type-confusion-regression3", "java.lang.NullPointerException\n", |
| "merge-blocks-regression", "java.lang.NullPointerException\n"), |
| Version.V4_0_4, |
| ImmutableMap.of( |
| "bad-codegen", "java.lang.NullPointerException\n", |
| "type-confusion-regression2", "java.lang.NullPointerException\n", |
| "type-confusion-regression3", "java.lang.NullPointerException\n", |
| "merge-blocks-regression", "java.lang.NullPointerException\n"), |
| Version.V13_0_0, |
| ImmutableMap.of( |
| "bad-codegen", |
| StringUtils.lines( |
| "java.lang.NullPointerException: Attempt to read from field 'Test Test.a'" |
| + " on a null object reference in method 'Test TestObject.a(Test," |
| + " Test, Test, Test, boolean)'"), |
| "type-confusion-regression3", |
| StringUtils.lines( |
| "java.lang.NullPointerException: Attempt to read from field 'byte[]" |
| + " Test.a' on a null object reference in method 'int" |
| + " TestObject.a(Test, Test)'")), |
| Version.V14_0_0, |
| ImmutableMap.of( |
| "bad-codegen", |
| StringUtils.lines( |
| "java.lang.NullPointerException: Attempt to read from field 'Test Test.a'" |
| + " on a null object reference in method 'Test TestObject.a(Test," |
| + " Test, Test, Test, boolean)'"), |
| "type-confusion-regression3", |
| StringUtils.lines( |
| "java.lang.NullPointerException: Attempt to read from field 'byte[]" |
| + " Test.a' on a null object reference in method 'int" |
| + " TestObject.a(Test, Test)'"))); |
| |
| // Tests where the input fails with a verification error on Dalvik instead of the |
| // expected runtime exception. |
| private static final Map<DexVm.Version, List<String>> dalvikVerificationErrors = |
| ImmutableMap.of( |
| Version.V4_4_4, |
| ImmutableList.of( |
| // The invokes are in fact invalid, but the test expects the current Art behavior |
| // of throwing an IncompatibleClassChange exception. Dalvik fails to verify. |
| "illegal-invokes"), |
| Version.V4_0_4, |
| ImmutableList.of( |
| // The invokes are in fact invalid, but the test expects the current Art behavior |
| // of throwing an IncompatibleClassChange exception. Dalvik fails to verify. |
| "illegal-invokes")); |
| |
| private Set<String> failingOnX8 = ImmutableSet.of( |
| // Contains use of register as both an int and a float. |
| "regression/33336471" |
| ); |
| |
| @Rule |
| public TestDescriptionWatcher watcher = new TestDescriptionWatcher(); |
| |
| private final TestParameters parameters; |
| private final String directoryName; |
| private final String dexFileName; |
| private final String expectedOutput; |
| |
| public R8RunSmaliTestsTest(TestParameters parameters, String name) { |
| this.parameters = parameters; |
| String expectedOutput = tests.get(name); |
| if (customProcessedOutputExpectation.containsKey(parameters.asDexRuntime().getVersion()) |
| && customProcessedOutputExpectation |
| .get(parameters.asDexRuntime().getVersion()) |
| .containsKey(name)) { |
| // If the original and the processed code have different expected output, only run |
| // the code produced by R8. |
| expectedOutput = |
| customProcessedOutputExpectation.get(parameters.asDexRuntime().getVersion()).get(name); |
| } |
| this.directoryName = name; |
| this.dexFileName = name.substring(name.lastIndexOf('/') + 1) + ".dex"; |
| this.expectedOutput = expectedOutput; |
| } |
| |
| @Test |
| public void SmaliTest() throws Exception { |
| Path originalDexFile = Paths.get(SMALI_DIR, directoryName, dexFileName); |
| |
| if (failingOnX8.contains(directoryName)) { |
| thrown.expect(CompilationFailedException.class); |
| } |
| |
| Version version = parameters.asDexRuntime().getVersion(); |
| boolean dalvikVerificationError = |
| dalvikVerificationErrors.containsKey(version) |
| && dalvikVerificationErrors.get(version).contains(directoryName); |
| boolean originalFailing = |
| (originalFailingOnArtVersions.containsKey(version) |
| && originalFailingOnArtVersions.get(version).contains(directoryName)); |
| Path testJar = Paths.get(SMALI_DIR, directoryName, "Test.jar"); |
| boolean testJarExists = testJar.toFile().exists(); |
| Path testJarDex = null; |
| if (testJarExists) { |
| testJarDex = |
| testForD8(Backend.DEX) |
| .setMinApi(parameters) |
| .addProgramFiles(testJar) |
| .compile() |
| .writeToZip(); |
| } |
| testForR8(parameters.getBackend()) |
| .addKeepAllClassesRule() |
| .addProgramDexFileData(Files.readAllBytes(originalDexFile)) |
| .applyIf(testJarExists, p -> p.addProgramFiles(testJar)) |
| .addDontWarn(missingClasses.getOrDefault(directoryName, Collections.emptySet())) |
| .setMinApi(parameters) |
| .compile() |
| .run(parameters.getRuntime(), "Test") |
| .applyIf( |
| dalvikVerificationError, |
| r -> r.assertFailureWithErrorThatThrows(VerifyError.class), |
| r -> r.assertSuccessWithOutput(expectedOutput)); |
| |
| // Also run the original DEX if possible. |
| if (!dalvikVerificationError && !originalFailing) { |
| ImmutableList.Builder<String> dexListBuilder = ImmutableList.builder(); |
| dexListBuilder.add(originalDexFile.toString()); |
| if (testJarExists) { |
| dexListBuilder.add(testJarDex.toString()); |
| } |
| String originalOutput = |
| ToolHelper.runArtNoVerificationErrors( |
| dexListBuilder.build(), "Test", null, parameters.getRuntime().asDex().getVm()); |
| assertEquals(expectedOutput, originalOutput); |
| } |
| } |
| } |