| // 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.Arrays; | |
| 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.rules.TemporaryFolder; | |
| 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(); | |
| private static final String SMALI_DIR = ToolHelper.SMALI_BUILD_DIR; | |
| 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 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 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" | |
| ) | |
| ); | |
| // Tests where the input fails with a verification error on Dalvik instead of the | |
| // expected runtime exception. | |
| private static Map<DexVm.Version, List<String>> dalvikVerificationError = 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" | |
| ) | |
| ); | |
| // Tests where the original smali code runs on Art, but fails after R8 processing | |
| private static Map<String, List<String>> failingOnArtVersions = ImmutableMap.of( | |
| // This list is currently empty! | |
| ); | |
| private Set<String> failingOnX8 = ImmutableSet.of( | |
| // Contains use of register as both an int and a float. | |
| "regression/33336471" | |
| ); | |
| @Rule | |
| public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); | |
| @Rule | |
| public TestDescriptionWatcher watcher = new TestDescriptionWatcher(); | |
| @Parameters(name = "{0}") | |
| public static Collection<String[]> data() { | |
| return Arrays.asList(new String[][]{ | |
| {"arithmetic", | |
| StringUtils.lines("-1", "3", "2", "3", "3.0", "1", "0", "-131580", "-131580", "2", "4", | |
| "-2")}, | |
| {"controlflow", | |
| StringUtils.lines("2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2", "1", "2")}, | |
| {"fibonacci", StringUtils.lines("55", "55", "55", "55")}, | |
| {"fill-array-data", "[1, 2, 3][4, 5, 6]"}, | |
| {"filled-new-array", "[1, 2, 3][4, 5, 6][1, 2, 3, 4, 5, 6][6, 5, 4, 3, 2, 1]"}, | |
| {"packed-switch", "12345"}, | |
| {"sparse-switch", "12345"}, | |
| {"unreachable-code-1", "777"}, | |
| {"multiple-returns", | |
| StringUtils.lines("TFtf", "1", "4611686018427387904", "true", "false")}, | |
| {"try-catch", ""}, | |
| {"phi-removal-regression", StringUtils.lines("returnBoolean")}, | |
| {"overlapping-long-registers", | |
| StringUtils.lines("-9151314442816847872", "-9151314442816319488")}, | |
| {"type-confusion-regression", | |
| StringUtils.lines("java.lang.RuntimeException: Test.<init>()")}, | |
| {"type-confusion-regression2", | |
| StringUtils.lines("java.lang.NullPointerException: Attempt to read from null array")}, | |
| {"type-confusion-regression3", | |
| StringUtils.lines( | |
| "java.lang.NullPointerException: Attempt to read from field 'byte[] Test.a'" + | |
| " on a null object reference")}, | |
| {"type-confusion-regression4", ""}, | |
| {"type-confusion-regression5", StringUtils.lines("java.lang.RuntimeException: getId()I")}, | |
| {"chain-of-loops", StringUtils.lines("java.lang.RuntimeException: f(II)")}, | |
| {"new-instance-and-init", StringUtils.lines("Test(0)", "Test(0)", "Test(0)")}, | |
| {"bad-codegen", | |
| StringUtils.lines("java.lang.NullPointerException: Attempt to read from field " + | |
| "'Test Test.a' on a null object reference")}, | |
| {"merge-blocks-regression", | |
| StringUtils.lines("java.lang.NullPointerException: Attempt to invoke virtual" | |
| + " method 'Test Test.bW_()' on a null object reference")}, | |
| {"self-is-catch-block", StringUtils.lines("100", "-1")}, | |
| {"infinite-loop", ""}, | |
| {"regression/33336471", | |
| StringUtils.lines("START", "0", "2", "LOOP", "1", "2", "LOOP", "2", "2", "DONE", | |
| "START", "0", "2", "LOOP", "1", "2", "LOOP", "2", "2", "DONE")}, | |
| {"regression/33846227", ""}, | |
| {"illegal-invokes", StringUtils.lines("ICCE", "ICCE")}, | |
| }); | |
| } | |
| private String directoryName; | |
| private String dexFileName; | |
| private String expectedOutput; | |
| public R8RunSmaliTestsTest(String name, String expectedOutput) { | |
| 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); | |
| Path outputPath = temp.getRoot().toPath().resolve("classes.dex"); | |
| if (failingOnX8.contains(directoryName)) { | |
| thrown.expect(CompilationFailedException.class); | |
| } | |
| testForR8(Backend.DEX) | |
| .addKeepAllClassesRule() | |
| .addProgramDexFileData(Files.readAllBytes(originalDexFile)) | |
| .addDontWarn(missingClasses.getOrDefault(directoryName, Collections.emptySet())) | |
| .compile() | |
| .writeToZip(outputPath); | |
| if (!ToolHelper.artSupported()) { | |
| return; | |
| } | |
| String mainClass = "Test"; | |
| String generated = outputPath.toString(); | |
| String output = ""; | |
| DexVm.Version dexVmVersion = ToolHelper.getDexVm().getVersion(); | |
| if (dalvikVerificationError.containsKey(dexVmVersion) | |
| && dalvikVerificationError.get(dexVmVersion).contains(directoryName)) { | |
| try { | |
| ToolHelper.runArtNoVerificationErrors(generated, mainClass); | |
| } catch (AssertionError e) { | |
| assert e.toString().contains("VerifyError"); | |
| } | |
| return; | |
| } else if (originalFailingOnArtVersions.containsKey(dexVmVersion) | |
| && originalFailingOnArtVersions.get(dexVmVersion).contains(directoryName)) { | |
| // If the original smali code fails on the target VM, only run the code produced by R8. | |
| output = ToolHelper.runArtNoVerificationErrors(generated, mainClass); | |
| } else if (customProcessedOutputExpectation.containsKey(dexVmVersion) | |
| && customProcessedOutputExpectation.get(dexVmVersion).containsKey(directoryName)) { | |
| // If the original and the processed code have different expected output, only run | |
| // the code produced by R8. | |
| expectedOutput = | |
| customProcessedOutputExpectation.get(dexVmVersion).get(directoryName); | |
| output = ToolHelper.runArtNoVerificationErrors(generated, mainClass); | |
| } else { | |
| if (failingOnArtVersions.containsKey(dexVmVersion) | |
| && failingOnArtVersions.get(dexVmVersion).contains(directoryName)) { | |
| thrown.expect(Throwable.class); | |
| } | |
| output = | |
| ToolHelper | |
| .checkArtOutputIdentical(originalDexFile.toString(), generated, mainClass, null); | |
| } | |
| assertEquals(expectedOutput, output); | |
| } | |
| } |