blob: 21a8719f1dcf048d7197a4b1af8eb9553ef2a3ac [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.ir.optimize.checkcast;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
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.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class CheckCastRemovalTest extends JasminTestBase {
@Parameterized.Parameters(name = "Backend: {0}")
public static Backend[] data() {
return ToolHelper.getBackends();
}
private final Backend backend;
private final String CLASS_NAME = "Example";
public CheckCastRemovalTest(Backend backend) {
this.backend = backend;
}
@Test
public void exactMatch() throws Exception {
JasminBuilder builder = new JasminBuilder();
ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
classBuilder.addDefaultConstructor();
MethodSignature main = classBuilder.addMainMethod(
".limit stack 3",
".limit locals 1",
"new Example",
"dup",
"invokespecial Example/<init>()V",
"checkcast Example", // Gone
"return");
List<String> pgConfigs = ImmutableList.of(
"-keep class " + CLASS_NAME + " { *; }",
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, o -> o.enableClassInlining = false, backend);
checkCheckCasts(app, main, null);
checkRuntime(builder, app, CLASS_NAME);
}
@Test
public void upCasts() throws Exception {
JasminBuilder builder = new JasminBuilder();
// A < B < C
ClassBuilder c = builder.addClass("C");
c.addDefaultConstructor();
ClassBuilder b = builder.addClass("B", "C");
b.addDefaultConstructor();
ClassBuilder a = builder.addClass("A", "B");
a.addDefaultConstructor();
ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
MethodSignature main = classBuilder.addMainMethod(
".limit stack 3",
".limit locals 1",
"new A",
"dup",
"invokespecial A/<init>()V",
"checkcast B", // Gone
"checkcast C", // Gone
"return");
List<String> pgConfigs = ImmutableList.of(
"-keep class " + CLASS_NAME + " { *; }",
"-keep class A { *; }",
"-keep class B { *; }",
"-keep class C { *; }",
"-dontshrink");
AndroidApp app =
compileWithR8(builder, pgConfigs, opts -> opts.enableClassInlining = false, backend);
checkCheckCasts(app, main, null);
checkRuntime(builder, app, CLASS_NAME);
}
@Test
public void bothUpAndDowncast() throws Exception {
JasminBuilder builder = new JasminBuilder();
ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
MethodSignature main = classBuilder.addMainMethod(
".limit stack 4",
".limit locals 1",
"iconst_1",
"anewarray java/lang/String", // args parameter
"dup",
"iconst_0",
"ldc \"a string\"",
"aastore",
"checkcast [Ljava/lang/Object;", // This upcast can be removed.
"iconst_0",
"aaload",
"checkcast java/lang/String", // Then, this downcast can be removed, too.
"invokevirtual java/lang/String/length()I",
"return");
// That is, both checkcasts should be removed together or kept together.
List<String> pgConfigs = ImmutableList.of(
"-keep class " + CLASS_NAME + " { *; }",
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null, backend);
checkCheckCasts(app, main, null);
checkRuntime(builder, app, CLASS_NAME);
}
@Test
public void nullCast() throws Exception {
JasminBuilder builder = new JasminBuilder();
ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
classBuilder.addField("public", "fld", "Ljava/lang/String;", null);
MethodSignature main = classBuilder.addMainMethod(
".limit stack 3",
".limit locals 1",
"aconst_null",
"checkcast Example", // Should be kept
"getfield Example.fld Ljava/lang/String;",
"return");
List<String> pgConfigs = ImmutableList.of(
"-keep class " + CLASS_NAME + " { *; }",
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null, backend);
checkCheckCasts(app, main, null);
checkRuntimeException(builder, app, CLASS_NAME, "NullPointerException");
}
private void checkCheckCasts(AndroidApp app, MethodSignature main, String maybeType)
throws ExecutionException, IOException {
MethodSubject method = getMethodSubject(app, CLASS_NAME, main);
assertTrue(method.isPresent());
// Make sure there is only a single CheckCast with specified type, or no CheckCasts (if
// maybeType == null).
Iterator<InstructionSubject> iterator = method.iterateInstructions();
boolean found = maybeType == null;
while (iterator.hasNext()) {
InstructionSubject instruction = iterator.next();
if (!instruction.isCheckCast()) {
continue;
}
assertTrue(!found && instruction.isCheckCast(maybeType));
found = true;
}
assertTrue(found);
}
private void checkRuntime(JasminBuilder builder, AndroidApp app, String className)
throws Exception {
String normalOutput = runOnJava(builder, className);
String optimizedOutput;
if (backend == Backend.DEX) {
optimizedOutput = runOnArt(app, className);
} else {
assert backend == Backend.CF;
optimizedOutput = runOnJava(app, className);
}
assertEquals(normalOutput, optimizedOutput);
}
private void checkRuntimeException(
JasminBuilder builder, AndroidApp app, String className, String exceptionName)
throws Exception {
ProcessResult javaOutput = runOnJavaRaw(builder, className);
assertEquals(1, javaOutput.exitCode);
assertTrue(javaOutput.stderr.contains(exceptionName));
ProcessResult output;
if (backend == Backend.DEX) {
output = runOnArtRaw(app, className);
} else {
assert backend == Backend.CF;
output = runOnJavaRaw(app, className, Collections.emptyList());
}
assertEquals(1, output.exitCode);
assertTrue(output.stderr.contains(exceptionName));
}
}