| // 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; | |
| } | |
| } |