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