blob: 5f00fc9b02c0e914d24c2e79329823cf56245ac0 [file] [log] [blame]
// Copyright (c) 2016, 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.utils;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.ArrayList;
import java.util.List;
public class ArtErrorParser {
private static final String VERIFICATION_ERROR_HEADER = "Verification error in ";
private static final String METHOD_EXCEEDS_INSTRUCITON_LIMIT =
"Method exceeds compiler instruction limit: ";
public static class ArtErrorParserException extends Exception {
public ArtErrorParserException(String message) {
super(message);
}
}
private static class ParseInput {
public final String input;
public int pos = 0;
public ParseInput(String input) {
this.input = input;
}
public boolean hasNext() {
return pos < input.length();
}
public char next() {
assert hasNext();
return input.charAt(pos++);
}
public char peek() {
return input.charAt(pos);
}
public void whitespace() {
while (peek() == ' ') {
next();
}
}
public String until(int end) {
String result = input.substring(pos, end);
pos = end;
return result;
}
public String until(char match) throws ArtErrorParserException {
int end = input.indexOf(match, pos);
if (end < 0) {
throw new ArtErrorParserException("Expected to find " + match + " at " + pos);
}
return until(end);
}
public void check(char expected) throws ArtErrorParserException {
if (peek() != expected) {
throw new ArtErrorParserException("Expected " + expected + " found " + pos);
}
}
}
public static abstract class ArtErrorInfo {
public abstract int consumedLines();
public abstract String getMessage();
public abstract String dump(CodeInspector inspector, boolean markLocation);
}
private static class ArtMethodError extends ArtErrorInfo {
public final String className;
public final String methodName;
public final String methodReturn;
public final List<String> methodFormals;
public final String methodSignature;
public final String errorMessage;
public final List<Long> locations;
private final int consumedLines;
public ArtMethodError(String className,
String methodName, String methodReturn, List<String> methodFormals,
String methodSignature, String errorMessage,
List<Long> locations,
int consumedLines) {
this.className = className;
this.methodName = methodName;
this.methodReturn = methodReturn;
this.methodFormals = methodFormals;
this.methodSignature = methodSignature;
this.errorMessage = errorMessage;
this.locations = locations;
this.consumedLines = consumedLines;
}
@Override
public int consumedLines() {
return consumedLines;
}
@Override
public String getMessage() {
StringBuilder builder = new StringBuilder();
builder.append("\n")
.append(VERIFICATION_ERROR_HEADER)
.append(methodSignature)
.append(":\n")
.append(errorMessage);
return builder.toString();
}
@Override
public String dump(CodeInspector inspector, boolean markLocation) {
ClassSubject clazz = inspector.clazz(className);
MethodSubject method = clazz.method(methodReturn, methodName, methodFormals);
DexEncodedMethod dex = method.getMethod();
if (dex == null) {
StringBuilder builder = new StringBuilder("Failed to lookup method: ");
builder.append(className).append(".").append(methodName);
StringUtils.append(builder, methodFormals);
return builder.toString();
}
String code = method.getMethod().codeToString();
if (markLocation && !locations.isEmpty()) {
for (Long location : locations) {
String locationString = "" + location + ":";
code = code.replaceFirst(":(\\s*) " + locationString, ":$1*" + locationString);
}
}
return code;
}
}
public static List<ArtErrorInfo> parse(String message) throws ArtErrorParserException {
List<ArtErrorInfo> errors = new ArrayList<>();
String[] lines = message.split("\n");
for (int i = 0; i < lines.length; i++) {
ArtErrorInfo error = parse(lines, i);
if (error != null) {
errors.add(error);
i += error.consumedLines();
}
}
return errors;
}
private static ArtErrorInfo parse(String[] lines, final int line) throws ArtErrorParserException {
String methodSig = null;
StringBuilder errorMessageContent = new StringBuilder();
int currentLine = line;
{
int index = lines[currentLine].indexOf(VERIFICATION_ERROR_HEADER);
if (index >= 0) {
// Read/skip the header line.
String lineContent = lines[currentLine++].substring(index);
// Append the content of each subsequent line that can be parsed as an "error message".
for (; currentLine < lines.length; ++currentLine) {
lineContent = lines[currentLine].substring(index);
String[] parts = lineContent.split(":");
if (parts.length == 2) {
if (methodSig == null) {
methodSig = parts[0];
errorMessageContent.append(parts[1]);
} else if (methodSig.equals(parts[0])) {
errorMessageContent.append(parts[1]);
} else {
break;
}
} else if (parts.length >= 3) {
if (methodSig == null) {
methodSig = parts[1];
for (int i = 2; i < parts.length; ++i) {
errorMessageContent.append(parts[i]);
}
} else if (methodSig.equals(parts[1])) {
for (int i = 2; i < parts.length; ++i) {
errorMessageContent.append(parts[i]);
}
} else {
break;
}
} else {
break;
}
}
if (methodSig == null) {
throw new ArtErrorParserException("Unexpected art error message: " + lineContent);
}
}
}
if (methodSig == null) {
int index = lines[currentLine].indexOf(METHOD_EXCEEDS_INSTRUCITON_LIMIT);
if (index >= 0) {
String lineContent = lines[currentLine++].substring(index);
String[] parts = lineContent.split(":");
if (parts.length == 2) {
errorMessageContent.append(lineContent);
methodSig = parts[1].substring(parts[1].indexOf(" in ") + 4);
} else {
throw new ArtErrorParserException("Unexpected art error message parts: " + parts);
}
}
}
// Return if we failed to identify an error description.
if (methodSig == null) {
return null;
}
String errorMessage = errorMessageContent.toString();
ParseInput input = new ParseInput(methodSig);
String methodReturn = parseType(input);
String[] qualifiedName = parseQualifiedName(input);
List<String> methodFormals = parseTypeList(input);
List<Long> locations = parseLocations(errorMessage);
return new ArtMethodError(getClassName(qualifiedName), getMethodName(qualifiedName),
methodReturn, methodFormals, methodSig, errorMessage, locations, currentLine - line);
}
private static String getClassName(String[] parts) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < parts.length - 1; i++) {
if (i != 0) {
builder.append('.');
}
builder.append(parts[i]);
}
return builder.toString();
}
private static String getMethodName(String[] parts) {
return parts[parts.length - 1];
}
private static String parseType(ParseInput input) throws ArtErrorParserException {
input.whitespace();
String type = input.until(' ');
assert !type.contains("<");
input.whitespace();
return type;
}
private static String[] parseQualifiedName(ParseInput input) throws ArtErrorParserException {
input.whitespace();
return input.until('(').split("\\.");
}
private static List<String> parseTypeList(ParseInput input) throws ArtErrorParserException {
input.check('(');
input.next();
String content = input.until(')').trim();
if (content.isEmpty()) {
return new ArrayList<>();
}
String[] rawList = content.split(",");
List<String> types = new ArrayList<>();
for (String type : rawList) {
types.add(type.trim());
}
input.check(')');
input.next();
return types;
}
private static boolean isHexChar(char c) {
return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F';
}
private static List<Long> parseLocations(String input) {
List<Long> locations = new ArrayList<>();
int length = input.length();
int start = 0;
while (start < length && (start = input.indexOf("0x", start)) >= 0) {
int end = start + 2;
while (end < length && isHexChar(input.charAt(end))) {
++end;
}
long l = Long.parseLong(input.substring(start + 2, end), 16);
if (l >= 0) {
locations.add(l);
}
start = end;
}
return locations;
}
}