|  | // 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); | 
|  | } | 
|  | } |