| // Copyright (c) 2019, 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.retrace.internal; |
| |
| import com.android.tools.r8.errors.Unreachable; |
| import com.android.tools.r8.retrace.StackTraceLineParser; |
| import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.ClassNameType; |
| import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy.StackTraceElementStringProxyBuilder; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| public class StackTraceRegularExpressionParser |
| implements StackTraceLineParser<String, StackTraceElementStringProxy> { |
| |
| // This is a slight modification of the default regular expression shown for proguard retrace |
| // that allow for retracing classes in the form <class>: lorem ipsum... |
| // Seems like Proguard retrace is expecting the form "Caused by: <class>". |
| public static final String DEFAULT_REGULAR_EXPRESSION = |
| "(?:.*?\\bat\\s+%c\\.%m\\s*\\(%s(?::%l)?\\)\\s*(?:~\\[.*\\])?)" |
| + "|(?:(?:(?:%c|.*)?[:\"]\\s+)?%c(?::.*)?)"; |
| |
| private final Pattern compiledPattern; |
| |
| private static final int NO_MATCH = -1; |
| |
| private final SourceFileLineNumberGroup sourceFileLineNumberGroup = |
| new SourceFileLineNumberGroup(); |
| private final List<RegularExpressionGroupHandler> handlers; |
| private final TypeNameGroup typeNameGroup = new TypeNameGroup(); |
| private final BinaryNameGroup binaryNameGroup = new BinaryNameGroup(); |
| private final SourceFileGroup sourceFileGroup = new SourceFileGroup(); |
| private final LineNumberGroup lineNumberGroup = new LineNumberGroup(); |
| private final FieldOrReturnTypeGroup fieldOrReturnTypeGroup = new FieldOrReturnTypeGroup(); |
| private final MethodArgumentsGroup methodArgumentsGroup = new MethodArgumentsGroup(); |
| private final MethodNameGroup methodNameGroup = new MethodNameGroup(); |
| private final FieldNameGroup fieldNameGroup = new FieldNameGroup(); |
| |
| private static final String CAPTURE_GROUP_PREFIX = "captureGroup"; |
| private static final int FIRST_CAPTURE_GROUP_INDEX = 0; |
| |
| public StackTraceRegularExpressionParser() { |
| this(DEFAULT_REGULAR_EXPRESSION); |
| } |
| |
| public StackTraceRegularExpressionParser(String regularExpression) { |
| handlers = new ArrayList<>(); |
| StringBuilder refinedRegularExpressionBuilder = new StringBuilder(); |
| registerGroups( |
| regularExpression, refinedRegularExpressionBuilder, handlers, FIRST_CAPTURE_GROUP_INDEX); |
| compiledPattern = Pattern.compile(refinedRegularExpressionBuilder.toString()); |
| } |
| |
| @Override |
| public StackTraceElementStringProxy parse(String stackTraceLine) { |
| StackTraceElementStringProxyBuilder proxyBuilder = |
| StackTraceElementStringProxy.builder(stackTraceLine); |
| Matcher matcher = compiledPattern.matcher(stackTraceLine); |
| if (matcher.matches()) { |
| boolean seenMatchedClassHandler = false; |
| for (RegularExpressionGroupHandler handler : handlers) { |
| if (seenMatchedClassHandler && handler.isClassHandler()) { |
| continue; |
| } |
| if (handler.matchHandler(proxyBuilder, matcher)) { |
| seenMatchedClassHandler |= handler.isClassHandler(); |
| } |
| } |
| } |
| return proxyBuilder.build(); |
| } |
| |
| private int registerGroups( |
| String regularExpression, |
| StringBuilder refinedRegularExpression, |
| List<RegularExpressionGroupHandler> handlers, |
| int captureGroupIndex) { |
| int lastCommittedIndex = 0; |
| boolean seenPercentage = false; |
| boolean escaped = false; |
| for (int i = 0; i < regularExpression.length(); i++) { |
| if (seenPercentage) { |
| assert !escaped; |
| final RegularExpressionGroup group = getGroupFromVariable(regularExpression.charAt(i)); |
| refinedRegularExpression.append(regularExpression, lastCommittedIndex, i - 1); |
| if (group.isSynthetic()) { |
| captureGroupIndex = |
| registerGroups( |
| group.subExpression(), refinedRegularExpression, handlers, captureGroupIndex); |
| } else { |
| String captureGroupName = CAPTURE_GROUP_PREFIX + (captureGroupIndex++); |
| refinedRegularExpression |
| .append("(?<") |
| .append(captureGroupName) |
| .append(">") |
| .append(group.subExpression()) |
| .append(")"); |
| handlers.add(group.createHandler(captureGroupName)); |
| } |
| lastCommittedIndex = i + 1; |
| seenPercentage = false; |
| } else { |
| seenPercentage = !escaped && regularExpression.charAt(i) == '%'; |
| escaped = !escaped && regularExpression.charAt(i) == '\\'; |
| } |
| } |
| refinedRegularExpression.append( |
| regularExpression, lastCommittedIndex, regularExpression.length()); |
| return captureGroupIndex; |
| } |
| |
| private boolean isTypeOrBinarySeparator(String regularExpression, int startIndex, int endIndex) { |
| assert endIndex < regularExpression.length(); |
| if (startIndex + 1 != endIndex) { |
| return false; |
| } |
| if (regularExpression.charAt(startIndex) != '\\') { |
| return false; |
| } |
| return regularExpression.charAt(startIndex + 1) == '.' |
| || regularExpression.charAt(startIndex + 1) == '/'; |
| } |
| |
| private RegularExpressionGroup getGroupFromVariable(char variable) { |
| switch (variable) { |
| case 'c': |
| return typeNameGroup; |
| case 'C': |
| return binaryNameGroup; |
| case 'm': |
| return methodNameGroup; |
| case 'f': |
| return fieldNameGroup; |
| case 's': |
| return sourceFileGroup; |
| case 'l': |
| return lineNumberGroup; |
| case 'S': |
| return sourceFileLineNumberGroup; |
| case 't': |
| return fieldOrReturnTypeGroup; |
| case 'a': |
| return methodArgumentsGroup; |
| default: |
| throw new Unreachable("Unexpected variable: " + variable); |
| } |
| } |
| |
| private interface RegularExpressionGroupHandler { |
| |
| boolean matchHandler(StackTraceElementStringProxyBuilder builder, Matcher matcher); |
| |
| default boolean isClassHandler() { |
| return false; |
| } |
| } |
| |
| private abstract static class RegularExpressionGroup { |
| |
| abstract String subExpression(); |
| |
| abstract RegularExpressionGroupHandler createHandler(String captureGroup); |
| |
| boolean isSynthetic() { |
| return false; |
| } |
| } |
| |
| // TODO(b/145731185): Extend support for identifiers with strings inside back ticks. |
| private static final String javaIdentifierSegment = |
| "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"; |
| |
| private static final String METHOD_NAME_REGULAR_EXPRESSION = |
| "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))"; |
| |
| abstract static class ClassNameGroup extends RegularExpressionGroup { |
| |
| abstract ClassNameType getClassNameType(); |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return new RegularExpressionGroupHandler() { |
| @Override |
| public boolean matchHandler(StackTraceElementStringProxyBuilder builder, Matcher matcher) { |
| int startOfGroup = matcher.start(captureGroup); |
| if (startOfGroup == NO_MATCH) { |
| return false; |
| } |
| String typeName = matcher.group(captureGroup); |
| if (typeName.equals("Suppressed")) { |
| // Ensure we do not map supressed. |
| return false; |
| } |
| builder.registerClassName(startOfGroup, matcher.end(captureGroup), getClassNameType()); |
| return true; |
| } |
| |
| @Override |
| public boolean isClassHandler() { |
| return true; |
| } |
| }; |
| } |
| } |
| |
| private static class TypeNameGroup extends ClassNameGroup { |
| |
| @Override |
| String subExpression() { |
| return "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment; |
| } |
| |
| @Override |
| ClassNameType getClassNameType() { |
| return ClassNameType.TYPENAME; |
| } |
| } |
| |
| private static class BinaryNameGroup extends ClassNameGroup { |
| |
| @Override |
| String subExpression() { |
| return "(?:" + javaIdentifierSegment + "\\/)*" + javaIdentifierSegment; |
| } |
| |
| @Override |
| ClassNameType getClassNameType() { |
| return ClassNameType.BINARY; |
| } |
| } |
| |
| private static class MethodNameGroup extends RegularExpressionGroup { |
| |
| @Override |
| String subExpression() { |
| return METHOD_NAME_REGULAR_EXPRESSION; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (builder, matcher) -> { |
| int startOfGroup = matcher.start(captureGroup); |
| if (startOfGroup == NO_MATCH) { |
| return false; |
| } |
| builder.registerMethodName(startOfGroup, matcher.end(captureGroup)); |
| return true; |
| }; |
| } |
| } |
| |
| private static class FieldNameGroup extends RegularExpressionGroup { |
| |
| @Override |
| String subExpression() { |
| return javaIdentifierSegment; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (builder, matcher) -> { |
| int startOfGroup = matcher.start(captureGroup); |
| if (startOfGroup == NO_MATCH) { |
| return false; |
| } |
| builder.registerFieldName(startOfGroup, matcher.end(captureGroup)); |
| return true; |
| }; |
| } |
| } |
| |
| private static class SourceFileGroup extends RegularExpressionGroup { |
| |
| @Override |
| String subExpression() { |
| String anyNonDigitNonColonChar = "^\\d:"; |
| String anyNonColonChar = "^:"; |
| String colonWithNonDigitSuffix = ":+[" + anyNonDigitNonColonChar + "]"; |
| return "((?:(?:(?:" + colonWithNonDigitSuffix + "))|(?:[" + anyNonColonChar + "]))+)?"; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (builder, matcher) -> { |
| int startOfGroup = matcher.start(captureGroup); |
| if (startOfGroup == NO_MATCH) { |
| return false; |
| } |
| builder.registerSourceFile(startOfGroup, matcher.end(captureGroup)); |
| return true; |
| }; |
| } |
| } |
| |
| private static class LineNumberGroup extends RegularExpressionGroup { |
| |
| @Override |
| String subExpression() { |
| return "\\d*"; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (builder, matcher) -> { |
| int startOfGroup = matcher.start(captureGroup); |
| if (startOfGroup == NO_MATCH) { |
| return false; |
| } |
| builder.registerLineNumber(startOfGroup, matcher.end(captureGroup)); |
| return true; |
| }; |
| } |
| } |
| |
| private static class SourceFileLineNumberGroup extends RegularExpressionGroup { |
| |
| @Override |
| String subExpression() { |
| return "%s(?::%l)?"; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| throw new Unreachable("Should never be called"); |
| } |
| |
| @Override |
| boolean isSynthetic() { |
| return true; |
| } |
| } |
| |
| private static final String JAVA_TYPE_REGULAR_EXPRESSION = |
| "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment + "[\\[\\]]*"; |
| |
| private static class FieldOrReturnTypeGroup extends RegularExpressionGroup { |
| |
| @Override |
| String subExpression() { |
| return JAVA_TYPE_REGULAR_EXPRESSION; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (builder, matcher) -> { |
| int startOfGroup = matcher.start(captureGroup); |
| if (startOfGroup == NO_MATCH) { |
| return false; |
| } |
| builder.registerFieldOrReturnType(startOfGroup, matcher.end(captureGroup)); |
| return true; |
| }; |
| } |
| } |
| |
| private static class MethodArgumentsGroup extends RegularExpressionGroup { |
| |
| @Override |
| String subExpression() { |
| return "((" + JAVA_TYPE_REGULAR_EXPRESSION + "\\,)*" + JAVA_TYPE_REGULAR_EXPRESSION + ")?"; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (builder, matcher) -> { |
| int startOfGroup = matcher.start(captureGroup); |
| if (startOfGroup == NO_MATCH) { |
| return false; |
| } |
| builder.registerMethodArguments(startOfGroup, matcher.end(captureGroup)); |
| return true; |
| }; |
| } |
| } |
| } |