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