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