blob: b42be41785e7fbf2e6b044664485d18561e69460 [file] [log] [blame]
// Copyright (c) 2018, 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.shaking;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.core.IsNot.not;
import com.android.tools.r8.D8TestRunResult;
import com.android.tools.r8.DXTestRunResult;
import com.android.tools.r8.ProguardTestRunResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
import com.android.tools.r8.jasmin.JasminTestBase;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Collection;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class InvalidTypesTest extends JasminTestBase {
private enum Compiler {
DX,
D8,
JAVAC,
PROGUARD,
R8,
R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES
}
private enum Mode {
NO_INVOKE,
INVOKE_UNVERIFIABLE_METHOD,
INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS;
public String instruction() {
switch (this) {
case NO_INVOKE:
return "";
case INVOKE_UNVERIFIABLE_METHOD:
return "invokestatic UnverifiableClass/unverifiableMethod()V";
case INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS:
return "invokestatic UnverifiableClass/verifiableMethod()V";
default:
throw new Unreachable();
}
}
}
private final TestParameters parameters;
private final Mode mode;
private final boolean useInterface;
public InvalidTypesTest(TestParameters parameters, Mode mode, boolean useInterface) {
this.parameters = parameters;
this.mode = mode;
this.useInterface = useInterface;
}
@Parameters(name = "{0}, mode: {1}, use interface: {2}")
public static Collection<Object[]> parameters() {
return buildParameters(
getTestParameters().withAllRuntimes().build(), Mode.values(), BooleanUtils.values());
}
@Test
public void test() throws Exception {
JasminBuilder jasminBuilder = new JasminBuilder();
if (useInterface) {
jasminBuilder.addInterface("A");
} else {
jasminBuilder.addClass("A").addDefaultConstructor();
}
jasminBuilder.addClass("B").addDefaultConstructor();
jasminBuilder.addInterface("I");
ClassBuilder mainClass = jasminBuilder.addClass("TestClass");
mainClass.addStaticField("f", "LA;");
mainClass.addStaticField("g", "LI;");
mainClass.addMainMethod(
".limit stack 2",
".limit locals 1",
// Print "Hello!".
"getstatic java/lang/System/out Ljava/io/PrintStream;",
"ldc \"Hello!\"",
"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
// Invoke method on UnverifiableClass, depending on the mode.
mode.instruction(),
// Print "Goodbye!".
"getstatic java/lang/System/out Ljava/io/PrintStream;",
"ldc \"Goodbye!\"",
"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
"return");
mainClass
.staticMethodBuilder("m", ImmutableList.of(), "V")
.setCode(
// Print "Unexpected outcome of getstatic" if reading TestClass.f yields `null`.
"getstatic TestClass/f LA;",
"ifnonnull Label0",
"getstatic java/lang/System/out Ljava/io/PrintStream;",
"ldc \"Unexpected outcome of getstatic\"",
"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
// Print "Unexpected outcome of checkcast" if TestClass.f can be casted to A.
"Label0:",
"getstatic TestClass/f LA;",
"checkcast A", // (should throw)
"pop",
"getstatic java/lang/System/out Ljava/io/PrintStream;",
"ldc \"Unexpected outcome of checkcast\"",
"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
"goto Label1",
"Catch:",
"pop",
// Print "Unexpected outcome of instanceof" if TestClass.f is an instance of A.
"Label1:",
"getstatic TestClass/f LA;",
"instanceof A", // (should return false)
"ifeq Return",
"getstatic java/lang/System/out Ljava/io/PrintStream;",
"ldc \"Unexpected outcome of instanceof\"",
"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
// Return.
"Return:",
"return",
".catch java/lang/Throwable from Label0 to Catch using Catch")
.build();
ClassBuilder UnverifiableClass = jasminBuilder.addClass("UnverifiableClass");
UnverifiableClass.staticMethodBuilder("<clinit>", ImmutableList.of(), "V")
.setCode("new B", "dup", "invokespecial B/<init>()V", "putstatic TestClass/g LI;", "return")
.build();
UnverifiableClass.staticMethodBuilder("unverifiableMethod", ImmutableList.of(), "V")
.setCode(
"new B",
"dup",
"invokespecial B/<init>()V",
"putstatic TestClass/f LA;",
"invokestatic TestClass/m()V",
"return")
.build();
UnverifiableClass.staticMethodBuilder("verifiableMethod", ImmutableList.of(), "V")
.setCode(
"getstatic java/lang/System/out Ljava/io/PrintStream;",
"ldc \"In verifiable method!\"",
"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
"return")
.build();
Path inputJar = temp.getRoot().toPath().resolve("input.jar");
jasminBuilder.writeJar(inputJar);
if (parameters.isCfRuntime()) {
TestRunResult<?> jvmResult = testForJvm().addClasspath(inputJar).run(mainClass.name);
checkTestRunResult(jvmResult, Compiler.JAVAC);
ProguardTestRunResult proguardResult =
testForProguard()
.addProgramFiles(inputJar)
.addKeepMainRule(mainClass.name)
.addKeepRules("-keep class TestClass { public static I g; }")
.run(mainClass.name);
checkTestRunResult(proguardResult, Compiler.PROGUARD);
} else {
assert parameters.isDexRuntime();
DXTestRunResult dxResult = testForDX().addProgramFiles(inputJar).run(mainClass.name);
checkTestRunResult(dxResult, Compiler.DX);
D8TestRunResult d8Result = testForD8().addProgramFiles(inputJar).run(mainClass.name);
checkTestRunResult(d8Result, Compiler.D8);
}
R8TestRunResult r8Result =
testForR8(parameters.getBackend())
.addProgramFiles(inputJar)
.addKeepMainRule(mainClass.name)
.addKeepRules(
"-keep class TestClass { public static I g; }",
"-neverinline class TestClass { public static void m(); }")
.enableProguardTestOptions()
.addOptionsModification(
options -> {
if (mode == Mode.INVOKE_UNVERIFIABLE_METHOD) {
options.testing.allowTypeErrors = true;
}
})
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), mainClass.name);
checkTestRunResult(r8Result, Compiler.R8);
R8TestRunResult r8ResultWithUninstantiatedTypeOptimizationForInterfaces =
testForR8(parameters.getBackend())
.addProgramFiles(inputJar)
.addKeepMainRule(mainClass.name)
.addKeepRules(
"-keep class TestClass { public static I g; }",
"-neverinline class TestClass { public static void m(); }")
.enableProguardTestOptions()
.addOptionsModification(
options -> {
if (mode == Mode.INVOKE_UNVERIFIABLE_METHOD) {
options.testing.allowTypeErrors = true;
}
options.enableUninstantiatedTypeOptimizationForInterfaces = true;
})
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), mainClass.name);
checkTestRunResult(
r8ResultWithUninstantiatedTypeOptimizationForInterfaces,
Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES);
}
private void checkTestRunResult(TestRunResult<?> result, Compiler compiler) {
switch (mode) {
case NO_INVOKE:
result.assertSuccessWithOutput(getExpectedOutput(compiler));
break;
case INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS:
if (useInterface) {
result.assertSuccessWithOutput(getExpectedOutput(compiler));
} else {
if (compiler == Compiler.R8
|| compiler == Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES
|| compiler == Compiler.PROGUARD) {
result.assertSuccessWithOutput(getExpectedOutput(compiler));
} else {
result
.assertFailureWithOutput(getExpectedOutput(compiler))
.assertFailureWithErrorThatMatches(getMatcherForExpectedError());
}
}
break;
case INVOKE_UNVERIFIABLE_METHOD:
if (useInterface) {
result.assertSuccessWithOutput(getExpectedOutput(compiler));
} else {
if (compiler == Compiler.R8
|| compiler == Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES) {
result
.assertFailureWithOutput(getExpectedOutput(compiler))
.assertFailureWithErrorThatMatches(
allOf(
containsString("java.lang.NullPointerException"),
not(containsString("java.lang.VerifyError"))));
} else {
result
.assertFailureWithOutput(getExpectedOutput(compiler))
.assertFailureWithErrorThatMatches(getMatcherForExpectedError());
}
}
break;
default:
throw new Unreachable();
}
}
private String getExpectedOutput(Compiler compiler) {
if (mode == Mode.NO_INVOKE) {
return StringUtils.joinLines("Hello!", "Goodbye!", "");
}
if (mode == Mode.INVOKE_VERIFIABLE_METHOD_ON_UNVERIFIABLE_CLASS) {
if (useInterface) {
return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
} else {
if (compiler == Compiler.R8
|| compiler == Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES
|| compiler == Compiler.PROGUARD) {
// The unverifiable method has been removed as a result of tree shaking, so the code does
// not fail with a verification error when trying to load class `UnverifiableClass`.
return StringUtils.joinLines("Hello!", "In verifiable method!", "Goodbye!", "");
} else {
// The code fails with a verification error because the verifiableMethod() is being called
// on `UnverifiableClass`, which does not verify due to unverifiableMethod().
return StringUtils.joinLines("Hello!", "");
}
}
}
assert mode == Mode.INVOKE_UNVERIFIABLE_METHOD;
if (useInterface) {
if (compiler == Compiler.R8_ENABLE_UNININSTANTATED_TYPE_OPTIMIZATION_FOR_INTERFACES) {
return StringUtils.joinLines(
"Hello!",
"Unexpected outcome of getstatic",
"Unexpected outcome of checkcast",
"Goodbye!",
"");
} else if (compiler == Compiler.R8 || compiler == Compiler.PROGUARD) {
return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
} else if (compiler == Compiler.DX || compiler == Compiler.D8) {
if (ToolHelper.getDexVm().getVersion() == Version.V4_0_4
|| ToolHelper.getDexVm().getVersion() == Version.V4_4_4) {
return StringUtils.joinLines("Hello!", "Goodbye!", "");
} else if (ToolHelper.getDexVm().getVersion() == Version.V7_0_0) {
return StringUtils.joinLines(
"Hello!",
"Unexpected outcome of checkcast",
"Unexpected outcome of instanceof",
"Goodbye!",
"");
} else {
return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
}
} else {
assert compiler == Compiler.JAVAC;
return StringUtils.joinLines("Hello!", "Goodbye!", "");
}
} else {
return StringUtils.joinLines("Hello!", "");
}
}
private Matcher<String> getMatcherForExpectedError() {
if (parameters.isCfRuntime()) {
return allOf(
containsString("java.lang.VerifyError"),
containsString("Bad type in putfield/putstatic"));
}
assert parameters.isDexRuntime();
return allOf(
containsString("java.lang.VerifyError"),
anyOf(
containsString("register v0 has type Precise Reference: B but expected Reference: A"),
containsString("VFY: storing type 'LB;' into field type 'LA;'")));
}
}