blob: 384043c7b8f3c7264509126bcddfe6c11d7c14e9 [file] [log] [blame]
// 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);
}
}
}