blob: bb59573da3c1920fca9496cafd938d1fd348365d [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;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
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.utils.AbortException;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.BooleanUtils;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
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 B116282409 extends JasminTestBase {
private final Backend backend;
private final boolean enableVerticalClassMerging;
@Rule public ExpectedException exception = ExpectedException.none();
@Parameters(name = "Backend: {0}, vertical class merging: {1}")
public static Collection<Object[]> data() {
return buildParameters(ToolHelper.getBackends(), BooleanUtils.values());
}
public B116282409(Backend backend, boolean enableVerticalClassMerging) {
this.backend = backend;
this.enableVerticalClassMerging = enableVerticalClassMerging;
}
@Test
public void test() throws Exception {
JasminBuilder jasminBuilder = new JasminBuilder();
// Create a class A with a default constructor that prints "In A.<init>()".
ClassBuilder classBuilder = jasminBuilder.addClass("A");
classBuilder.addMethod(
"public",
"<init>",
ImmutableList.of(),
"V",
".limit stack 2",
".limit locals 1",
"aload_0",
"invokespecial java/lang/Object/<init>()V",
"getstatic java/lang/System/out Ljava/io/PrintStream;",
"ldc \"In A.<init>()\"",
"invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
"return");
// Create a class B that inherits from A.
classBuilder = jasminBuilder.addClass("B", "A");
// Also add a simple method that the class inliner would inline.
classBuilder.addVirtualMethod(
"m", "I", ".limit stack 5", ".limit locals 5", "bipush 42", "ireturn");
// Add a test class that initializes an instance of B using A.<init>.
classBuilder = jasminBuilder.addClass("TestClass");
classBuilder.addMainMethod(
".limit stack 5",
".limit locals 5",
"getstatic java/lang/System/out Ljava/io/PrintStream;",
"new B",
"dup",
"invokespecial A/<init>()V", // NOTE: usually B.<init>
"invokevirtual B/m()I",
"invokevirtual java/io/PrintStream/println(I)V",
"return");
// Build app.
if (enableVerticalClassMerging) {
exception.expect(CompilationFailedException.class);
exception.expectCause(
new CustomExceptionMatcher(
"Unable to rewrite `invoke-direct A.<init>(new B, ...)` in method "
+ "`void TestClass.main(java.lang.String[])` after type `A` was merged into `B`.",
"Please add the following rule to your Proguard configuration file: "
+ "`-keep,allowobfuscation class A`."));
}
AndroidApp output =
compileWithR8(
jasminBuilder.build(),
keepMainProguardConfiguration("TestClass"),
options -> options.enableVerticalClassMerging = enableVerticalClassMerging,
backend);
assertFalse(enableVerticalClassMerging);
// Run app.
ProcessResult vmResult = runOnVMRaw(output, "TestClass", backend);
if (backend == Backend.CF) {
// Verify that the input code does not run with java.
ProcessResult javaResult = runOnJavaRaw(jasminBuilder, "TestClass");
assertNotEquals(0, javaResult.exitCode);
assertThat(javaResult.stderr, containsString("VerifyError"));
assertThat(javaResult.stderr, containsString("Call to wrong initialization method"));
// The same should be true for the generated program.
assertNotEquals(0, vmResult.exitCode);
assertThat(vmResult.stderr, containsString("VerifyError"));
assertThat(vmResult.stderr, containsString("Call to wrong initialization method"));
} else {
assert backend == Backend.DEX;
if (ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
assertNotEquals(0, vmResult.exitCode);
assertThat(
vmResult.stderr,
containsString("VFY: invoke-direct <init> on super only allowed for 'this' in <init>"));
} else {
assertEquals(0, vmResult.exitCode);
assertEquals(
String.join(System.lineSeparator(), "In A.<init>()", "42", ""), vmResult.stdout);
}
}
}
private static class CustomExceptionMatcher extends BaseMatcher<Throwable> {
private List<String> messages;
public CustomExceptionMatcher(String... messages) {
this.messages = Arrays.asList(messages);
}
@Override
public void describeTo(Description description) {
description
.appendText("a string containing ")
.appendText(
String.join(
", ", messages.stream().map(m -> "\"" + m + "\"").collect(Collectors.toList())));
}
@Override
public boolean matches(Object o) {
if (o instanceof AbortException) {
AbortException exception = (AbortException) o;
if (exception.getMessage() != null
&& messages.stream().allMatch(message -> exception.getMessage().contains(message))) {
return true;
}
if (exception.getCause() != null) {
return matches(exception.getCause());
}
}
return false;
}
}
}