blob: 9efecb46f42f9e07c062f665de942c552428ac43 [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.debuginfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersBuilder;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debuginfo.InliningWithoutPositionsTestSourceDump.Location;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
import com.android.tools.r8.naming.Range;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class InliningWithoutPositionsTestRunner extends TestBase {
private static final String TEST_CLASS = "InliningWithoutPositionsTestSource";
private static final String TEST_PACKAGE = "com.android.tools.r8.debuginfo";
private static final String MAIN_CLASS = TEST_PACKAGE + "." + TEST_CLASS;
@ClassRule public static TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
private final TestParameters parameters;
private final boolean mainPos;
private final boolean foo1Pos;
private final boolean barPos;
private final boolean foo2Pos;
private final Location throwLocation;
@Parameters(name = "{0}: main/foo1/bar/foo2 positions: {1}/{2}/{3}/{4}, throwLocation: {5}")
public static Collection<Object[]> data() {
List<Object[]> testCases = new ArrayList<>();
for (TestParameters parameters :
TestParametersBuilder.builder().withAllRuntimes().withApiLevel(AndroidApiLevel.B).build()) {
for (int i = 0; i < 16; ++i) {
for (Location throwLocation : Location.values()) {
if (throwLocation != Location.MAIN) {
testCases.add(
new Object[] {
parameters, (i & 1) != 0, (i & 2) != 0, (i & 4) != 0, (i & 8) != 0, throwLocation
});
}
}
}
}
return testCases;
}
public InliningWithoutPositionsTestRunner(
TestParameters parameters,
boolean mainPos,
boolean foo1Pos,
boolean barPos,
boolean foo2Pos,
Location throwLocation) {
this.parameters = parameters;
this.mainPos = mainPos;
this.foo1Pos = foo1Pos;
this.barPos = barPos;
this.foo2Pos = foo2Pos;
this.throwLocation = throwLocation;
}
@Test
public void testStackTrace() throws Exception {
// See InliningWithoutPositionsTestSourceDump for the code compiled here.
Path testClassDir = temp.newFolder().toPath();
Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
Files.write(
testClassPath,
InliningWithoutPositionsTestSourceDump.dump(
mainPos, foo1Pos, barPos, foo2Pos, throwLocation));
R8TestRunResult result =
testForR8(parameters.getBackend())
.setMinApi(parameters.getApiLevel())
.addProgramFiles(testClassPath)
.setMode(CompilationMode.RELEASE)
.addKeepMainRule(MAIN_CLASS)
.noMinification()
.addKeepAttributeSourceFile()
.addKeepAttributeLineNumberTable()
.addOptionsModification(options -> options.inliningInstructionLimit = 40)
.run(parameters.getRuntime(), MAIN_CLASS)
.assertFailure();
// Verify stack trace.
// result.stderr looks like this:
//
// Exception in thread "main" java.lang.RuntimeException: <FOO1-exception>
// at
// com.android.tools.r8.debuginfo.InliningWithoutPositionsTestSource.main(InliningWithoutPositionsTestSource.java:1)
String[] lines = result.getStdErr().split("\n");
// The line containing 'java.lang.RuntimeException' should contain the expected message, which
// is "LOCATIONCODE-exception>"
int i = 0;
boolean foundException = false;
for (; i < lines.length && !foundException; ++i) {
boolean hasExpectedException = lines[i].contains("<" + throwLocation + "-exception>");
if (lines[i].contains("java.lang.RuntimeException")) {
assertTrue(hasExpectedException);
foundException = true;
} else {
assertFalse(hasExpectedException);
}
}
assertTrue(foundException);
// The next line, the stack trace, must always be the same, indicating 'main' and line = 1 or 2.
Assert.assertTrue(i < lines.length);
String line = lines[i].trim();
assertTrue(line.startsWith("at " + TEST_PACKAGE + "." + TEST_CLASS + "." + "main"));
// It must contain the '<source-file>:1' or ':2', if we're throwing at foo2.
int expectedLineNumber = throwLocation == Location.FOO2 ? 2 : 1;
String expectedFilePos = TEST_CLASS + ".java:" + expectedLineNumber;
int idx = line.indexOf(expectedFilePos);
assertTrue(idx >= 0);
// And the next character must be a non-digit or nothing.
int idxAfter = idx + expectedFilePos.length();
assertTrue(idxAfter == line.length() || !Character.isDigit(line.charAt(idxAfter)));
// Reading the Proguard map. An example map (only the relevant part, 'main'):
//
// 1:1:void bar():0:0 -> main
// 1:1:void foo(boolean):0 -> main
// 1:1:void main(java.lang.String[]):0 -> main
ClassNameMapper mapper = ClassNameMapper.mapperFromString(result.proguardMap());
assertNotNull(mapper);
ClassNamingForNameMapper classNaming = mapper.getClassNaming(TEST_PACKAGE + "." + TEST_CLASS);
assertNotNull(classNaming);
MappedRangesOfName rangesForMain = classNaming.mappedRangesByRenamedName.get("main");
assertNotNull(rangesForMain);
List<MappedRange> frames = rangesForMain.allRangesForLine(expectedLineNumber);
switch (throwLocation) {
case FOO1:
assertEquals(2, frames.size());
assertFrame(true, "foo", Location.FOO1, foo1Pos, frames.get(0));
break;
case BAR:
assertEquals(3, frames.size());
assertFrame(true, "bar", Location.BAR, barPos, frames.get(0));
assertFrame(false, "foo", Location.FOO1, foo1Pos, frames.get(1));
break;
case FOO2:
assertEquals(2, frames.size());
// If there's no foo2Pos then we expect foo1 pos at this location.
if (foo2Pos) {
assertFrame(true, "foo", Location.FOO2, true, frames.get(0));
} else {
assertFrame(true, "foo", Location.FOO1, foo1Pos, frames.get(0));
}
break;
default:
Assert.fail();
}
assertFrame(false, "main", Location.MAIN, mainPos, frames.get(frames.size() - 1));
}
private void assertFrame(
boolean innermostFrame,
String function,
Location location,
boolean hasPosition,
MappedRange range) {
assertEquals(function, range.signature.name);
int expectedLineNumber = hasPosition ? location.line : 0;
Object expectedOriginalRange =
innermostFrame ? new Range(expectedLineNumber, expectedLineNumber) : expectedLineNumber;
assertEquals(expectedOriginalRange, range.originalRange);
}
}