blob: 878cb7ceb9d936924a7eb24fe4161d5203de3c02 [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.neverreturnsnormally;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.DexInstructionSubject;
import com.android.tools.r8.utils.DexInspector.InstructionSubject;
import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.Iterator;
import java.util.function.BiConsumer;
import org.junit.Test;
public class NeverReturnsNormallyTest extends TestBase {
private void runTest(
BiConsumer<DexInspector, CompilationMode> inspection,
boolean enableClassInliner, CompilationMode mode) throws Exception {
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
builder.setMode(mode);
builder.addProguardConfiguration(
ImmutableList.of(
"-keep class " + TestClass.class.getCanonicalName() + "{",
" public static void main(java.lang.String[]);",
" *** test*(...);",
" *** throwToBeInlined(...);",
" *** outerTrivial(...);",
"}",
"",
"-checkdiscard class " + TestClass.class.getCanonicalName() + "{",
" *** assertRemoved(...);",
"}",
"",
"-dontobfuscate",
"-allowaccessmodification"
),
Origin.unknown());
AndroidApp app = ToolHelper.runR8(builder.build(),
opts -> opts.enableClassInlining = enableClassInliner);
inspection.accept(new DexInspector(app), mode);
// Run on Art to check generated code against verifier.
runOnArt(app, TestClass.class);
}
private void validate(DexInspector inspector, CompilationMode mode) {
ClassSubject clazz = inspector.clazz(TestClass.class);
assertTrue(clazz.isPresent());
// All calls to 'assertRemoved' are to be removed.
clazz.forAllMethods(method -> {
Iterator<InstructionSubject> instructions =
method.iterateInstructions(InstructionSubject::isInvoke);
while (instructions.hasNext()) {
InvokeInstructionSubject invoke = (InvokeInstructionSubject) instructions.next();
assertFalse(invoke.invokedMethod().name.toString().equals("assertRemoved"));
}
});
// Check the instruction used for testInlinedIntoVoidMethod
MethodSubject methodThrowToBeInlined =
clazz.method("int", "throwToBeInlined", ImmutableList.of());
assertTrue(methodThrowToBeInlined.isPresent());
Iterator<InstructionSubject> instructions = methodThrowToBeInlined.iterateInstructions();
// Call, followed by throw null.
assertTrue(nextInstruction(instructions).isConstString());
InstructionSubject insn = nextInstruction(instructions);
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("throwNpe"));
insn = nextInstruction(instructions);
assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
assertTrue(nextInstruction(instructions).isThrow());
assertFalse(instructions.hasNext());
// Check the instruction used for testInlinedIntoVoidMethod
MethodSubject methodTestInlinedIntoVoidMethod =
clazz.method("void", "testInlinedIntoVoidMethod", ImmutableList.of());
assertTrue(methodTestInlinedIntoVoidMethod.isPresent());
instructions = methodTestInlinedIntoVoidMethod.iterateInstructions();
if (mode == CompilationMode.DEBUG) {
// Not inlined call to throwToBeInlined.
insn = nextInstruction(instructions);
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("throwToBeInlined"));
} else {
// Inlined code from throwToBeInlined.
assertTrue(nextInstruction(instructions).isConstString());
insn = nextInstruction(instructions);
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("throwNpe"));
}
insn = nextInstruction(instructions);
assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
assertTrue(nextInstruction(instructions).isThrow());
assertFalse(instructions.hasNext());
// Check the instruction used for testInlinedIntoVoidMethod
MethodSubject methodOuterTrivial =
clazz.method("int", "outerTrivial", ImmutableList.of());
assertTrue(methodOuterTrivial.isPresent());
instructions = methodOuterTrivial.iterateInstructions();
// Call, followed by [nop, goto]
insn = nextInstruction(instructions);
assertTrue(insn.isInvoke());
assertTrue(((InvokeInstructionSubject) insn)
.invokedMethod().name.toString().equals("innerNotReachable"));
insn = nextInstruction(instructions);
assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
assertTrue(nextInstruction(instructions).isThrow());
assertFalse(instructions.hasNext());
}
private InstructionSubject nextInstruction(Iterator<InstructionSubject> instructions) {
assertTrue(instructions.hasNext());
return instructions.next();
}
@Test
public void test() throws Exception {
runTest(this::validate, true, CompilationMode.DEBUG);
runTest(this::validate, true, CompilationMode.RELEASE);
runTest(this::validate, false, CompilationMode.DEBUG);
runTest(this::validate, false, CompilationMode.RELEASE);
}
}