blob: 90fedbd188c03d87b7cc9d27beebe5f3b9d023c4 [file] [log] [blame]
// Copyright (c) 2019, 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.retrace.internal;
import static com.google.common.base.Predicates.not;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.retrace.RetraceInvalidStackTraceLineDiagnostics;
import com.android.tools.r8.retrace.StackTraceVisitor;
import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassNameType;
import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder;
import com.android.tools.r8.utils.DescriptorUtils;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
public final class PlainStackTraceVisitor
implements StackTraceVisitor<StackTraceElementStringProxy> {
private final List<String> stackTrace;
private final DiagnosticsHandler diagnosticsHandler;
public PlainStackTraceVisitor(List<String> stackTrace, DiagnosticsHandler diagnosticsHandler) {
this.stackTrace = stackTrace;
this.diagnosticsHandler = diagnosticsHandler;
}
@Override
public void forEach(Consumer<StackTraceElementStringProxy> consumer) {
for (int i = 0; i < stackTrace.size(); i++) {
consumer.accept(parseLine(i + 1, stackTrace.get(i)));
}
}
static int firstNonWhiteSpaceCharacterFromIndex(String line, int index) {
return firstFromIndex(line, index, not(Character::isWhitespace));
}
static int firstCharFromIndex(String line, int index, char ch) {
return firstFromIndex(line, index, c -> c == ch);
}
static int firstFromIndex(String line, int index, Predicate<Character> predicate) {
for (int i = index; i < line.length(); i++) {
if (predicate.test(line.charAt(i))) {
return i;
}
}
return line.length();
}
/**
* Captures a stack trace line of the following formats:
*
* <ul>
* <li>com.android.r8.R8Exception
* <li>com.android.r8.R8Exception: Problem when compiling program
* <li>Caused by: com.android.r8.R8InnerException: You have to write the program first
* <li>com.android.r8.R8InnerException: You have to write the program first
* </ul>
*
* <p>This will also contains false positives, such as
*
* <pre>
* W( 8207) VFY: unable to resolve static method 11: Lprivateinterfacemethods/I$-CC;....
* </pre>
*
* <p>The only invalid chars for type-identifiers for a java type-name is ';', '[' and '/', so we
* cannot really disregard the above line.
*
* <p>Caused by and Suppressed seems to not change based on locale, so we use these as markers.
*/
private static class ExceptionLine {
private static final String CAUSED_BY = "Caused by: ";
private static final String SUPPRESSED = "Suppressed: ";
private static StackTraceElementStringProxy tryParse(String line) {
if (line.isEmpty()) {
return null;
}
int firstNonWhiteSpaceChar = firstNonWhiteSpaceCharacterFromIndex(line, 0);
String description = "";
if (line.startsWith(CAUSED_BY, firstNonWhiteSpaceChar)) {
description = CAUSED_BY;
} else if (line.startsWith(SUPPRESSED, firstNonWhiteSpaceChar)) {
description = SUPPRESSED;
}
int exceptionStartIndex = firstNonWhiteSpaceChar + description.length();
int messageStartIndex = firstCharFromIndex(line, exceptionStartIndex, ':');
String className = line.substring(exceptionStartIndex, messageStartIndex);
if (!DescriptorUtils.isValidJavaType(className)) {
return null;
}
return StackTraceElementStringProxy.builder(line)
.registerClassName(exceptionStartIndex, messageStartIndex, ClassNameType.TYPENAME)
.build();
}
}
/**
* Captures a stack trace line on the following form
*
* <ul>
* <li>at dalvik.system.NativeStart.main(NativeStart.java:99)
* <li>at dalvik.system.NativeStart.main(:99)
* <li>at dalvik.system.NativeStart.main(Foo.java:)
* <li>at dalvik.system.NativeStart.main(Native Method)
* <li>at classloader/named_module@version/foo.bar.baz(:20)
* <li>at classloader//foo.bar.baz(:20)
* </ul>
*
* <p>Empirical evidence suggests that the "at" string is never localized.
*/
private static class AtLine {
private static StackTraceElementStringProxy tryParse(String line) {
// Check that the line is indented with some amount of white space.
if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
return null;
}
// Find the first non-white space character and check that we have the sequence 'a', 't', ' '.
int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
if (firstNonWhiteSpace + 2 >= line.length()
|| line.charAt(firstNonWhiteSpace) != 'a'
|| line.charAt(firstNonWhiteSpace + 1) != 't'
|| line.charAt(firstNonWhiteSpace + 2) != ' ') {
return null;
}
int classClassLoaderOrModuleStartIndex =
firstNonWhiteSpaceCharacterFromIndex(line, firstNonWhiteSpace + 2);
if (classClassLoaderOrModuleStartIndex >= line.length()
|| classClassLoaderOrModuleStartIndex != firstNonWhiteSpace + 3) {
return null;
}
int parensStart = firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '(');
if (parensStart >= line.length()) {
return null;
}
int parensEnd = firstCharFromIndex(line, parensStart, ')');
if (parensEnd >= line.length()) {
return null;
}
if (firstNonWhiteSpaceCharacterFromIndex(line, parensEnd) == line.length()) {
return null;
}
int methodSeparator = line.lastIndexOf('.', parensStart);
if (methodSeparator <= classClassLoaderOrModuleStartIndex) {
return null;
}
int classStartIndex = classClassLoaderOrModuleStartIndex;
int classLoaderOrModuleEndIndex =
firstCharFromIndex(line, classClassLoaderOrModuleStartIndex, '/');
if (classLoaderOrModuleEndIndex < methodSeparator) {
int moduleEndIndex = firstCharFromIndex(line, classLoaderOrModuleEndIndex + 1, '/');
if (moduleEndIndex < methodSeparator) {
// The stack trace contains both a class loader and module
classStartIndex = moduleEndIndex + 1;
} else {
classStartIndex = classLoaderOrModuleEndIndex + 1;
}
}
StackTraceElementStringProxyBuilder builder =
StackTraceElementStringProxy.builder(line)
.registerClassName(classStartIndex, methodSeparator, ClassNameType.TYPENAME)
.registerMethodName(methodSeparator + 1, parensStart);
// Check if we have a filename and position.
int separatorIndex = line.lastIndexOf(':', parensEnd);
if (separatorIndex > -1 && separatorIndex < parensEnd) {
builder.registerSourceFile(parensStart + 1, separatorIndex);
builder.registerLineNumber(separatorIndex + 1, parensEnd);
} else {
builder.registerSourceFile(parensStart + 1, parensEnd);
}
return builder.build();
}
}
static class CircularReferenceLine {
private static final String CIRCULAR_REFERENCE = "[CIRCULAR REFERENCE:";
static StackTraceElementStringProxy tryParse(String line) {
// Check that the line is indented with some amount of white space.
if (line.length() == 0 || !Character.isWhitespace(line.charAt(0))) {
return null;
}
// Find the first non-white space character and check that we have the sequence
// '[CIRCULAR REFERENCE:Exception]'.
int firstNonWhiteSpace = firstNonWhiteSpaceCharacterFromIndex(line, 0);
if (!line.startsWith(CIRCULAR_REFERENCE, firstNonWhiteSpace)) {
return null;
}
int exceptionStartIndex = firstNonWhiteSpace + CIRCULAR_REFERENCE.length();
int lastBracketPosition = firstCharFromIndex(line, exceptionStartIndex, ']');
if (lastBracketPosition == line.length()) {
return null;
}
int onlyWhitespaceFromLastBracket =
firstNonWhiteSpaceCharacterFromIndex(line, lastBracketPosition + 1);
if (onlyWhitespaceFromLastBracket != line.length()) {
return null;
}
return StackTraceElementStringProxy.builder(line)
.registerClassName(exceptionStartIndex, lastBracketPosition, ClassNameType.TYPENAME)
.build();
}
}
private StackTraceElementStringProxy parseLine(int lineNumber, String line) {
if (line == null) {
diagnosticsHandler.error(RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
throw new RetraceAbortException();
}
// Most lines are 'at lines' so attempt to parse it first.
StackTraceElementStringProxy parsedLine = AtLine.tryParse(line);
if (parsedLine != null) {
return parsedLine;
}
parsedLine = ExceptionLine.tryParse(line);
if (parsedLine != null) {
return parsedLine;
}
parsedLine = CircularReferenceLine.tryParse(line);
if (parsedLine != null) {
return parsedLine;
}
return StackTraceElementStringProxy.builder(line).build();
}
}