1 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file 2 // for details. All rights reserved. Use of this source code is governed by a 3 // BSD-style license that can be found in the LICENSE file. 4 package com.android.tools.r8.utils; 5 6 import com.android.tools.r8.graph.DexEncodedMethod; 7 import com.android.tools.r8.utils.DexInspector.ClassSubject; 8 import com.android.tools.r8.utils.DexInspector.MethodSubject; 9 import java.util.ArrayList; 10 import java.util.List; 11 12 public class ArtErrorParser { 13 private static final String VERIFICATION_ERROR_HEADER = "Verification error in "; 14 private static final String METHOD_EXCEEDS_INSTRUCITON_LIMIT = 15 "Method exceeds compiler instruction limit: "; 16 17 public static class ArtErrorParserException extends Exception { ArtErrorParserException(String message)18 public ArtErrorParserException(String message) { 19 super(message); 20 } 21 } 22 23 private static class ParseInput { 24 public final String input; 25 public int pos = 0; 26 ParseInput(String input)27 public ParseInput(String input) { 28 this.input = input; 29 } 30 hasNext()31 public boolean hasNext() { 32 return pos < input.length(); 33 } 34 next()35 public char next() { 36 assert hasNext(); 37 return input.charAt(pos++); 38 } 39 peek()40 public char peek() { 41 return input.charAt(pos); 42 } 43 whitespace()44 public void whitespace() { 45 while (peek() == ' ') { 46 next(); 47 } 48 } 49 until(int end)50 public String until(int end) { 51 String result = input.substring(pos, end); 52 pos = end; 53 return result; 54 } 55 until(char match)56 public String until(char match) throws ArtErrorParserException { 57 int end = input.indexOf(match, pos); 58 if (end < 0) { 59 throw new ArtErrorParserException("Expected to find " + match + " at " + pos); 60 } 61 return until(end); 62 } 63 check(char expected)64 public void check(char expected) throws ArtErrorParserException { 65 if (peek() != expected) { 66 throw new ArtErrorParserException("Expected " + expected + " found " + pos); 67 } 68 } 69 } 70 71 public static abstract class ArtErrorInfo { consumedLines()72 public abstract int consumedLines(); getMessage()73 public abstract String getMessage(); dump(DexInspector inspector, boolean markLocation)74 public abstract String dump(DexInspector inspector, boolean markLocation); 75 } 76 77 private static class ArtMethodError extends ArtErrorInfo { 78 public final String className; 79 public final String methodName; 80 public final String methodReturn; 81 public final List<String> methodFormals; 82 public final String methodSignature; 83 public final String errorMessage; 84 public final List<Long> locations; 85 private final int consumedLines; 86 ArtMethodError(String className, String methodName, String methodReturn, List<String> methodFormals, String methodSignature, String errorMessage, List<Long> locations, int consumedLines)87 public ArtMethodError(String className, 88 String methodName, String methodReturn, List<String> methodFormals, 89 String methodSignature, String errorMessage, 90 List<Long> locations, 91 int consumedLines) { 92 this.className = className; 93 this.methodName = methodName; 94 this.methodReturn = methodReturn; 95 this.methodFormals = methodFormals; 96 this.methodSignature = methodSignature; 97 this.errorMessage = errorMessage; 98 this.locations = locations; 99 this.consumedLines = consumedLines; 100 } 101 102 @Override consumedLines()103 public int consumedLines() { 104 return consumedLines; 105 } 106 107 @Override getMessage()108 public String getMessage() { 109 StringBuilder builder = new StringBuilder(); 110 builder.append("\n") 111 .append(VERIFICATION_ERROR_HEADER) 112 .append(methodSignature) 113 .append(":\n") 114 .append(errorMessage); 115 return builder.toString(); 116 } 117 118 @Override dump(DexInspector inspector, boolean markLocation)119 public String dump(DexInspector inspector, boolean markLocation) { 120 ClassSubject clazz = inspector.clazz(className); 121 MethodSubject method = clazz.method(methodReturn, methodName, methodFormals); 122 DexEncodedMethod dex = method.getMethod(); 123 if (dex == null) { 124 StringBuilder builder = new StringBuilder("Failed to lookup method: "); 125 builder.append(className).append(".").append(methodName); 126 StringUtils.append(builder, methodFormals); 127 return builder.toString(); 128 } 129 130 String code = method.getMethod().codeToString(); 131 if (markLocation && !locations.isEmpty()) { 132 for (Long location : locations) { 133 String locationString = "" + location + ":"; 134 code = code.replaceFirst(":(\\s*) " + locationString, ":$1*" + locationString); 135 } 136 } 137 return code; 138 } 139 } 140 parse(String message)141 public static List<ArtErrorInfo> parse(String message) throws ArtErrorParserException { 142 List<ArtErrorInfo> errors = new ArrayList<>(); 143 String[] lines = message.split("\n"); 144 for (int i = 0; i < lines.length; i++) { 145 ArtErrorInfo error = parse(lines, i); 146 if (error != null) { 147 errors.add(error); 148 i += error.consumedLines(); 149 } 150 } 151 return errors; 152 } 153 parse(String[] lines, final int line)154 private static ArtErrorInfo parse(String[] lines, final int line) throws ArtErrorParserException { 155 String methodSig = null; 156 StringBuilder errorMessageContent = new StringBuilder(); 157 int currentLine = line; 158 { 159 int index = lines[currentLine].indexOf(VERIFICATION_ERROR_HEADER); 160 if (index >= 0) { 161 // Read/skip the header line. 162 String lineContent = lines[currentLine++].substring(index); 163 // Append the content of each subsequent line that can be parsed as an "error message". 164 for (; currentLine < lines.length; ++currentLine) { 165 lineContent = lines[currentLine].substring(index); 166 String[] parts = lineContent.split(":"); 167 if (parts.length == 2) { 168 if (methodSig == null) { 169 methodSig = parts[0]; 170 errorMessageContent.append(parts[1]); 171 } else if (methodSig.equals(parts[0])) { 172 errorMessageContent.append(parts[1]); 173 } else { 174 break; 175 } 176 } else if (parts.length >= 3) { 177 if (methodSig == null) { 178 methodSig = parts[1]; 179 for (int i = 2; i < parts.length; ++i) { 180 errorMessageContent.append(parts[i]); 181 } 182 } else if (methodSig.equals(parts[1])) { 183 for (int i = 2; i < parts.length; ++i) { 184 errorMessageContent.append(parts[i]); 185 } 186 } else { 187 break; 188 } 189 } else { 190 break; 191 } 192 } 193 if (methodSig == null) { 194 throw new ArtErrorParserException("Unexpected art error message: " + lineContent); 195 } 196 } 197 } 198 if (methodSig == null) { 199 int index = lines[currentLine].indexOf(METHOD_EXCEEDS_INSTRUCITON_LIMIT); 200 if (index >= 0) { 201 String lineContent = lines[currentLine++].substring(index); 202 String[] parts = lineContent.split(":"); 203 if (parts.length == 2) { 204 errorMessageContent.append(lineContent); 205 methodSig = parts[1].substring(parts[1].indexOf(" in ") + 4); 206 } else { 207 throw new ArtErrorParserException("Unexpected art error message parts: " + parts); 208 } 209 } 210 } 211 212 // Return if we failed to identify an error description. 213 if (methodSig == null) { 214 return null; 215 } 216 217 String errorMessage = errorMessageContent.toString(); 218 ParseInput input = new ParseInput(methodSig); 219 String methodReturn = parseType(input); 220 String[] qualifiedName = parseQualifiedName(input); 221 List<String> methodFormals = parseTypeList(input); 222 List<Long> locations = parseLocations(errorMessage); 223 return new ArtMethodError(getClassName(qualifiedName), getMethodName(qualifiedName), 224 methodReturn, methodFormals, methodSig, errorMessage, locations, currentLine - line); 225 } 226 getClassName(String[] parts)227 private static String getClassName(String[] parts) { 228 StringBuilder builder = new StringBuilder(); 229 for (int i = 0; i < parts.length - 1; i++) { 230 if (i != 0) { 231 builder.append('.'); 232 } 233 builder.append(parts[i]); 234 } 235 return builder.toString(); 236 } 237 getMethodName(String[] parts)238 private static String getMethodName(String[] parts) { 239 return parts[parts.length - 1]; 240 } 241 parseType(ParseInput input)242 private static String parseType(ParseInput input) throws ArtErrorParserException { 243 input.whitespace(); 244 String type = input.until(' '); 245 assert !type.contains("<"); 246 input.whitespace(); 247 return type; 248 } 249 parseQualifiedName(ParseInput input)250 private static String[] parseQualifiedName(ParseInput input) throws ArtErrorParserException { 251 input.whitespace(); 252 return input.until('(').split("\\."); 253 } 254 parseTypeList(ParseInput input)255 private static List<String> parseTypeList(ParseInput input) throws ArtErrorParserException { 256 input.check('('); 257 input.next(); 258 String content = input.until(')').trim(); 259 if (content.isEmpty()) { 260 return new ArrayList<>(); 261 } 262 String[] rawList = content.split(","); 263 List<String> types = new ArrayList<>(); 264 for (String type : rawList) { 265 types.add(type.trim()); 266 } 267 input.check(')'); 268 input.next(); 269 return types; 270 } 271 isHexChar(char c)272 private static boolean isHexChar(char c) { 273 return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'; 274 } 275 parseLocations(String input)276 private static List<Long> parseLocations(String input) { 277 List<Long> locations = new ArrayList<>(); 278 int length = input.length(); 279 int start = 0; 280 while (start < length && (start = input.indexOf("0x", start)) >= 0) { 281 int end = start + 2; 282 while (end < length && isHexChar(input.charAt(end))) { 283 ++end; 284 } 285 long l = Long.parseLong(input.substring(start + 2, end), 16); 286 if (l >= 0) { 287 locations.add(l); 288 } 289 start = end; 290 } 291 return locations; 292 } 293 } 294