| // 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; |
| |
| import com.android.tools.r8.DiagnosticsHandler; |
| import com.android.tools.r8.references.ClassReference; |
| import com.android.tools.r8.references.MethodReference; |
| import com.android.tools.r8.references.Reference; |
| import com.android.tools.r8.references.TypeReference; |
| import com.android.tools.r8.retrace.RetraceClassResult.Element; |
| import com.android.tools.r8.retrace.RetraceRegularExpression.RetraceString.RetraceStringBuilder; |
| import com.android.tools.r8.utils.DescriptorUtils; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.google.common.collect.Lists; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| |
| public class RetraceRegularExpression { |
| |
| private final RetraceBase retraceBase; |
| private final List<String> stackTrace; |
| private final DiagnosticsHandler diagnosticsHandler; |
| private final String regularExpression; |
| |
| private static final int NO_MATCH = -1; |
| |
| private final RegularExpressionGroup[] groups = |
| new RegularExpressionGroup[] { |
| new TypeNameGroup(), |
| new BinaryNameGroup(), |
| new MethodNameGroup(), |
| new FieldNameGroup(), |
| new SourceFileGroup(), |
| new LineNumberGroup(), |
| new FieldOrReturnTypeGroup(), |
| new MethodArgumentsGroup() |
| }; |
| |
| private static final String CAPTURE_GROUP_PREFIX = "captureGroup"; |
| |
| RetraceRegularExpression( |
| RetraceBase retraceBase, |
| List<String> stackTrace, |
| DiagnosticsHandler diagnosticsHandler, |
| String regularExpression) { |
| this.retraceBase = retraceBase; |
| this.stackTrace = stackTrace; |
| this.diagnosticsHandler = diagnosticsHandler; |
| this.regularExpression = regularExpression; |
| } |
| |
| public RetraceCommandLineResult retrace() { |
| List<RegularExpressionGroupHandler> handlers = new ArrayList<>(); |
| String regularExpression = registerGroups(this.regularExpression, handlers); |
| Pattern compiledPattern = Pattern.compile(regularExpression); |
| List<String> result = new ArrayList<>(); |
| for (String string : stackTrace) { |
| Matcher matcher = compiledPattern.matcher(string); |
| List<RetraceString> retracedStrings = |
| Lists.newArrayList(RetraceStringBuilder.create(string).build()); |
| if (matcher.matches()) { |
| for (RegularExpressionGroupHandler handler : handlers) { |
| retracedStrings = handler.handleMatch(retracedStrings, matcher, retraceBase); |
| } |
| } |
| if (retracedStrings.isEmpty()) { |
| // We could not find a match. Output the identity. |
| result.add(string); |
| } else { |
| boolean isAmbiguous = retracedStrings.size() > 1 && retracedStrings.get(0).isAmbiguous; |
| if (isAmbiguous) { |
| retracedStrings.sort(new RetraceLineComparator()); |
| } |
| ClassReference previousContext = null; |
| for (RetraceString retracedString : retracedStrings) { |
| String finalString = retracedString.getRetracedString(); |
| if (!isAmbiguous) { |
| result.add(finalString); |
| continue; |
| } |
| assert retracedString.getClassContext() != null; |
| ClassReference currentContext = retracedString.getClassContext().getClassReference(); |
| if (currentContext.equals(previousContext)) { |
| int firstNonWhitespaceCharacter = StringUtils.firstNonWhitespaceCharacter(finalString); |
| finalString = |
| finalString.substring(0, firstNonWhitespaceCharacter) |
| + "<OR> " |
| + finalString.substring(firstNonWhitespaceCharacter); |
| } |
| previousContext = currentContext; |
| result.add(finalString); |
| } |
| } |
| } |
| return new RetraceCommandLineResult(result); |
| } |
| |
| static class RetraceLineComparator extends AmbiguousComparator<RetraceString> { |
| |
| RetraceLineComparator() { |
| super( |
| (line, t) -> { |
| switch (t) { |
| case CLASS: |
| return line.getClassContext().getClassReference().getTypeName(); |
| case METHOD: |
| return line.getMethodContext().getMethodReference().getMethodName(); |
| case SOURCE: |
| return line.getSource(); |
| case LINE: |
| return line.getLineNumber() + ""; |
| default: |
| assert false; |
| } |
| throw new RuntimeException("Comparator key is unknown"); |
| }); |
| } |
| } |
| |
| private String registerGroups( |
| String regularExpression, List<RegularExpressionGroupHandler> handlers) { |
| int currentIndex = 0; |
| int captureGroupIndex = 0; |
| while (currentIndex < regularExpression.length()) { |
| RegularExpressionGroup firstGroup = null; |
| int firstIndexFromCurrent = regularExpression.length(); |
| for (RegularExpressionGroup group : groups) { |
| int nextIndexOf = regularExpression.indexOf(group.shortName(), currentIndex); |
| if (nextIndexOf > NO_MATCH && nextIndexOf < firstIndexFromCurrent) { |
| // Check if previous character in the regular expression is not \\ to ensure not |
| // overriding a matching on shortName. |
| if (nextIndexOf > 0 && regularExpression.charAt(nextIndexOf - 1) == '\\') { |
| continue; |
| } |
| firstGroup = group; |
| firstIndexFromCurrent = nextIndexOf; |
| } |
| } |
| if (firstGroup != null) { |
| String captureGroupName = CAPTURE_GROUP_PREFIX + (captureGroupIndex++); |
| String patternToInsert = "(?<" + captureGroupName + ">" + firstGroup.subExpression() + ")"; |
| regularExpression = |
| regularExpression.substring(0, firstIndexFromCurrent) |
| + patternToInsert |
| + regularExpression.substring( |
| firstIndexFromCurrent + firstGroup.shortName().length()); |
| handlers.add(firstGroup.createHandler(captureGroupName)); |
| firstIndexFromCurrent += patternToInsert.length(); |
| } |
| currentIndex = firstIndexFromCurrent; |
| } |
| return regularExpression; |
| } |
| |
| static class RetraceString { |
| |
| private final Element classContext; |
| private final ClassNameGroup classNameGroup; |
| private final ClassReference qualifiedContext; |
| private final RetraceMethodResult.Element methodContext; |
| private final TypeReference typeOrReturnTypeContext; |
| private final boolean hasTypeOrReturnTypeContext; |
| private final String retracedString; |
| private final int adjustedIndex; |
| private final boolean isAmbiguous; |
| private final int lineNumber; |
| private final String source; |
| |
| private RetraceString( |
| Element classContext, |
| ClassNameGroup classNameGroup, |
| ClassReference qualifiedContext, |
| RetraceMethodResult.Element methodContext, |
| TypeReference typeOrReturnTypeContext, |
| boolean hasTypeOrReturnTypeContext, |
| String retracedString, |
| int adjustedIndex, |
| boolean isAmbiguous, |
| int lineNumber, |
| String source) { |
| this.classContext = classContext; |
| this.classNameGroup = classNameGroup; |
| this.qualifiedContext = qualifiedContext; |
| this.methodContext = methodContext; |
| this.typeOrReturnTypeContext = typeOrReturnTypeContext; |
| this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext; |
| this.retracedString = retracedString; |
| this.adjustedIndex = adjustedIndex; |
| this.isAmbiguous = isAmbiguous; |
| this.lineNumber = lineNumber; |
| this.source = source; |
| } |
| |
| String getRetracedString() { |
| return retracedString; |
| } |
| |
| boolean hasTypeOrReturnTypeContext() { |
| return hasTypeOrReturnTypeContext; |
| } |
| |
| Element getClassContext() { |
| return classContext; |
| } |
| |
| RetraceMethodResult.Element getMethodContext() { |
| return methodContext; |
| } |
| |
| TypeReference getTypeOrReturnTypeContext() { |
| return typeOrReturnTypeContext; |
| } |
| |
| public ClassReference getQualifiedContext() { |
| return qualifiedContext; |
| } |
| |
| RetraceStringBuilder transform() { |
| return RetraceStringBuilder.create(this); |
| } |
| |
| public int getLineNumber() { |
| return lineNumber; |
| } |
| |
| public String getSource() { |
| return source; |
| } |
| |
| static class RetraceStringBuilder { |
| |
| private Element classContext; |
| private ClassNameGroup classNameGroup; |
| private ClassReference qualifiedContext; |
| private RetraceMethodResult.Element methodContext; |
| private TypeReference typeOrReturnTypeContext; |
| private boolean hasTypeOrReturnTypeContext; |
| private String retracedString; |
| private int adjustedIndex; |
| private boolean isAmbiguous; |
| private int lineNumber; |
| private String source; |
| |
| private int maxReplaceStringIndex = NO_MATCH; |
| |
| private RetraceStringBuilder( |
| Element classContext, |
| ClassNameGroup classNameGroup, |
| ClassReference qualifiedContext, |
| RetraceMethodResult.Element methodContext, |
| TypeReference typeOrReturnTypeContext, |
| boolean hasTypeOrReturnTypeContext, |
| String retracedString, |
| int adjustedIndex, |
| boolean isAmbiguous, |
| int lineNumber, |
| String source) { |
| this.classContext = classContext; |
| this.classNameGroup = classNameGroup; |
| this.qualifiedContext = qualifiedContext; |
| this.methodContext = methodContext; |
| this.typeOrReturnTypeContext = typeOrReturnTypeContext; |
| this.hasTypeOrReturnTypeContext = hasTypeOrReturnTypeContext; |
| this.retracedString = retracedString; |
| this.adjustedIndex = adjustedIndex; |
| this.isAmbiguous = isAmbiguous; |
| this.lineNumber = lineNumber; |
| this.source = source; |
| } |
| |
| static RetraceStringBuilder create(String string) { |
| return new RetraceStringBuilder( |
| null, null, null, null, null, false, string, 0, false, 0, ""); |
| } |
| |
| static RetraceStringBuilder create(RetraceString string) { |
| return new RetraceStringBuilder( |
| string.classContext, |
| string.classNameGroup, |
| string.qualifiedContext, |
| string.methodContext, |
| string.typeOrReturnTypeContext, |
| string.hasTypeOrReturnTypeContext, |
| string.retracedString, |
| string.adjustedIndex, |
| string.isAmbiguous, |
| string.lineNumber, |
| string.source); |
| } |
| |
| RetraceStringBuilder setClassContext(Element classContext, ClassNameGroup classNameGroup) { |
| this.classContext = classContext; |
| this.classNameGroup = classNameGroup; |
| return this; |
| } |
| |
| RetraceStringBuilder setMethodContext(RetraceMethodResult.Element methodContext) { |
| this.methodContext = methodContext; |
| return this; |
| } |
| |
| RetraceStringBuilder setTypeOrReturnTypeContext(TypeReference typeOrReturnTypeContext) { |
| hasTypeOrReturnTypeContext = true; |
| this.typeOrReturnTypeContext = typeOrReturnTypeContext; |
| return this; |
| } |
| |
| RetraceStringBuilder setQualifiedContext(ClassReference qualifiedContext) { |
| this.qualifiedContext = qualifiedContext; |
| return this; |
| } |
| |
| RetraceStringBuilder setAmbiguous(boolean isAmbiguous) { |
| this.isAmbiguous = isAmbiguous; |
| return this; |
| } |
| |
| RetraceStringBuilder setLineNumber(int lineNumber) { |
| this.lineNumber = lineNumber; |
| return this; |
| } |
| |
| RetraceStringBuilder setSource(String source) { |
| this.source = source; |
| return this; |
| } |
| |
| RetraceStringBuilder replaceInString(String oldString, String newString) { |
| int oldStringStartIndex = retracedString.indexOf(oldString); |
| assert oldStringStartIndex > NO_MATCH; |
| int oldStringEndIndex = oldStringStartIndex + oldString.length(); |
| return replaceInStringRaw(newString, oldStringStartIndex, oldStringEndIndex); |
| } |
| |
| RetraceStringBuilder replaceInString(String newString, int originalFrom, int originalTo) { |
| return replaceInStringRaw( |
| newString, originalFrom + adjustedIndex, originalTo + adjustedIndex); |
| } |
| |
| RetraceStringBuilder replaceInStringRaw(String newString, int from, int to) { |
| assert from <= to; |
| assert from > maxReplaceStringIndex; |
| String prefix = retracedString.substring(0, from); |
| String postFix = retracedString.substring(to); |
| this.retracedString = prefix + newString + postFix; |
| this.adjustedIndex = adjustedIndex + newString.length() - (to - from); |
| maxReplaceStringIndex = prefix.length() + newString.length(); |
| return this; |
| } |
| |
| RetraceString build() { |
| return new RetraceString( |
| classContext, |
| classNameGroup, |
| qualifiedContext, |
| methodContext, |
| typeOrReturnTypeContext, |
| hasTypeOrReturnTypeContext, |
| retracedString, |
| adjustedIndex, |
| isAmbiguous, |
| lineNumber, |
| source); |
| } |
| } |
| } |
| |
| private interface RegularExpressionGroupHandler { |
| |
| List<RetraceString> handleMatch( |
| List<RetraceString> strings, Matcher matcher, RetraceBase retraceBase); |
| } |
| |
| private abstract static class RegularExpressionGroup { |
| |
| abstract String shortName(); |
| |
| abstract String subExpression(); |
| |
| abstract RegularExpressionGroupHandler createHandler(String captureGroup); |
| } |
| |
| // TODO(b/145731185): Extend support for identifiers with strings inside back ticks. |
| private static final String javaIdentifierSegment = "[\\p{L}\\p{N}_\\p{Sc}]+"; |
| |
| private abstract static class ClassNameGroup extends RegularExpressionGroup { |
| |
| abstract String getClassName(ClassReference classReference); |
| |
| abstract ClassReference classFromMatch(String match); |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (strings, matcher, retraceBase) -> { |
| if (matcher.start(captureGroup) == NO_MATCH) { |
| return strings; |
| } |
| String typeName = matcher.group(captureGroup); |
| RetraceClassResult retraceResult = retraceBase.retrace(classFromMatch(typeName)); |
| List<RetraceString> retracedStrings = new ArrayList<>(); |
| for (RetraceString retraceString : strings) { |
| retraceResult.forEach( |
| element -> { |
| retracedStrings.add( |
| retraceString |
| .transform() |
| .setClassContext(element, this) |
| .setMethodContext(null) |
| .replaceInString( |
| getClassName(element.getClassReference()), |
| matcher.start(captureGroup), |
| matcher.end(captureGroup)) |
| .build()); |
| }); |
| } |
| return retracedStrings; |
| }; |
| } |
| } |
| |
| private static class TypeNameGroup extends ClassNameGroup { |
| |
| @Override |
| String shortName() { |
| return "%c"; |
| } |
| |
| @Override |
| String subExpression() { |
| return "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment; |
| } |
| |
| @Override |
| String getClassName(ClassReference classReference) { |
| return classReference.getTypeName(); |
| } |
| |
| @Override |
| ClassReference classFromMatch(String match) { |
| return Reference.classFromTypeName(match); |
| } |
| } |
| |
| private static class BinaryNameGroup extends ClassNameGroup { |
| |
| @Override |
| String shortName() { |
| return "%C"; |
| } |
| |
| @Override |
| String subExpression() { |
| return "(?:" + javaIdentifierSegment + "\\/)*" + javaIdentifierSegment; |
| } |
| |
| @Override |
| String getClassName(ClassReference classReference) { |
| return classReference.getBinaryName(); |
| } |
| |
| @Override |
| ClassReference classFromMatch(String match) { |
| return Reference.classFromBinaryName(match); |
| } |
| } |
| |
| private static class MethodNameGroup extends RegularExpressionGroup { |
| |
| @Override |
| String shortName() { |
| return "%m"; |
| } |
| |
| @Override |
| String subExpression() { |
| return "(?:(" + javaIdentifierSegment + "|\\<init\\>|\\<clinit\\>))"; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (strings, matcher, retraceBase) -> { |
| if (matcher.start(captureGroup) == NO_MATCH) { |
| return strings; |
| } |
| String methodName = matcher.group(captureGroup); |
| List<RetraceString> retracedStrings = new ArrayList<>(); |
| for (RetraceString retraceString : strings) { |
| if (retraceString.classContext == null) { |
| retracedStrings.add(retraceString); |
| continue; |
| } |
| retraceString |
| .getClassContext() |
| .lookupMethod(methodName) |
| .forEach( |
| element -> { |
| MethodReference methodReference = element.getMethodReference(); |
| if (retraceString.hasTypeOrReturnTypeContext()) { |
| if (methodReference.getReturnType() == null |
| && retraceString.getTypeOrReturnTypeContext() != null) { |
| return; |
| } else if (methodReference.getReturnType() != null |
| && !methodReference |
| .getReturnType() |
| .equals(retraceString.getTypeOrReturnTypeContext())) { |
| return; |
| } |
| } |
| RetraceStringBuilder newRetraceString = retraceString.transform(); |
| ClassReference existingClass = |
| retraceString.getClassContext().getClassReference(); |
| ClassReference holder = methodReference.getHolderClass(); |
| if (holder != existingClass) { |
| // The element is defined on another holder. |
| newRetraceString |
| .replaceInString( |
| newRetraceString.classNameGroup.getClassName(existingClass), |
| newRetraceString.classNameGroup.getClassName(holder)) |
| .setQualifiedContext(holder); |
| } |
| newRetraceString |
| .setMethodContext(element) |
| .setAmbiguous(element.getRetraceMethodResult().isAmbiguous()) |
| .replaceInString( |
| methodReference.getMethodName(), |
| matcher.start(captureGroup), |
| matcher.end(captureGroup)); |
| retracedStrings.add(newRetraceString.build()); |
| }); |
| } |
| return retracedStrings; |
| }; |
| } |
| } |
| |
| private static class FieldNameGroup extends RegularExpressionGroup { |
| |
| @Override |
| String shortName() { |
| return "%f"; |
| } |
| |
| @Override |
| String subExpression() { |
| return javaIdentifierSegment; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (strings, matcher, retraceBase) -> { |
| if (matcher.start(captureGroup) == NO_MATCH) { |
| return strings; |
| } |
| String methodName = matcher.group(captureGroup); |
| List<RetraceString> retracedStrings = new ArrayList<>(); |
| for (RetraceString retraceString : strings) { |
| if (retraceString.getClassContext() == null) { |
| retracedStrings.add(retraceString); |
| continue; |
| } |
| retraceString |
| .getClassContext() |
| .lookupField(methodName) |
| .forEach( |
| element -> { |
| RetraceStringBuilder newRetraceString = retraceString.transform(); |
| ClassReference existingClass = |
| retraceString.getClassContext().getClassReference(); |
| ClassReference holder = element.getFieldReference().getHolderClass(); |
| if (holder != existingClass) { |
| // The element is defined on another holder. |
| newRetraceString |
| .replaceInString( |
| newRetraceString.classNameGroup.getClassName(existingClass), |
| newRetraceString.classNameGroup.getClassName(holder)) |
| .setQualifiedContext(holder); |
| } |
| newRetraceString.replaceInString( |
| element.getFieldReference().getFieldName(), |
| matcher.start(captureGroup), |
| matcher.end(captureGroup)); |
| retracedStrings.add(newRetraceString.build()); |
| }); |
| } |
| return retracedStrings; |
| }; |
| } |
| } |
| |
| private static class SourceFileGroup extends RegularExpressionGroup { |
| |
| @Override |
| String shortName() { |
| return "%s"; |
| } |
| |
| @Override |
| String subExpression() { |
| return "(?:(\\w*[\\. ])?(\\w*)?)"; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (strings, matcher, retraceBase) -> { |
| if (matcher.start(captureGroup) == NO_MATCH) { |
| return strings; |
| } |
| String fileName = matcher.group(captureGroup); |
| List<RetraceString> retracedStrings = new ArrayList<>(); |
| for (RetraceString retraceString : strings) { |
| if (retraceString.classContext == null) { |
| retracedStrings.add(retraceString); |
| continue; |
| } |
| String newSourceFile = |
| retraceString.getQualifiedContext() != null |
| ? retraceBase.retraceSourceFile( |
| retraceString.classContext.getClassReference(), |
| fileName, |
| retraceString.getQualifiedContext(), |
| true) |
| : retraceString.classContext.retraceSourceFile(fileName, retraceBase); |
| retracedStrings.add( |
| retraceString |
| .transform() |
| .setSource(fileName) |
| .replaceInString( |
| newSourceFile, matcher.start(captureGroup), matcher.end(captureGroup)) |
| .build()); |
| } |
| return retracedStrings; |
| }; |
| } |
| } |
| |
| private class LineNumberGroup extends RegularExpressionGroup { |
| |
| @Override |
| String shortName() { |
| return "%l"; |
| } |
| |
| @Override |
| String subExpression() { |
| return "\\d*"; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (strings, matcher, retraceBase) -> { |
| if (matcher.start(captureGroup) == NO_MATCH) { |
| return strings; |
| } |
| String lineNumberAsString = matcher.group(captureGroup); |
| int lineNumber = |
| lineNumberAsString.isEmpty() ? NO_MATCH : Integer.parseInt(lineNumberAsString); |
| List<RetraceString> retracedStrings = new ArrayList<>(); |
| boolean seenRange = false; |
| for (RetraceString retraceString : strings) { |
| RetraceMethodResult.Element methodContext = retraceString.methodContext; |
| if (methodContext == null || methodContext.getMethodReference().isUnknown()) { |
| retracedStrings.add(retraceString); |
| continue; |
| } |
| if (methodContext.hasNoLineNumberRange()) { |
| continue; |
| } |
| seenRange = true; |
| Set<MethodReference> narrowedSet = |
| methodContext.getRetraceMethodResult().narrowByLine(lineNumber).stream() |
| .map(RetraceMethodResult.Element::getMethodReference) |
| .collect(Collectors.toSet()); |
| if (!narrowedSet.contains(methodContext.getMethodReference())) { |
| // Prune the retraceString since we now have line number information and this is not |
| // a part of the result. |
| diagnosticsHandler.info( |
| new StringDiagnostic( |
| "Pruning " |
| + retraceString.getRetracedString() |
| + " from result because method is not defined on line number " |
| + lineNumber)); |
| continue; |
| } |
| // The same method can be represented multiple times if it has multiple mappings. |
| if (!methodContext.containsMinifiedLineNumber(lineNumber)) { |
| diagnosticsHandler.info( |
| new StringDiagnostic( |
| "Pruning " |
| + retraceString.getRetracedString() |
| + " from result because method is not in range on line number " |
| + lineNumber)); |
| continue; |
| } |
| int originalLineNumber = methodContext.getOriginalLineNumber(lineNumber); |
| retracedStrings.add( |
| retraceString |
| .transform() |
| .setAmbiguous(false) |
| .setLineNumber(originalLineNumber) |
| .replaceInString( |
| originalLineNumber + "", |
| matcher.start(captureGroup), |
| matcher.end(captureGroup)) |
| .build()); |
| } |
| return seenRange ? retracedStrings : strings; |
| }; |
| } |
| } |
| |
| private static final String JAVA_TYPE_REGULAR_EXPRESSION = |
| "(" + javaIdentifierSegment + "\\.)*" + javaIdentifierSegment + "[\\[\\]]*"; |
| |
| private static class FieldOrReturnTypeGroup extends RegularExpressionGroup { |
| |
| @Override |
| String shortName() { |
| return "%t"; |
| } |
| |
| @Override |
| String subExpression() { |
| return JAVA_TYPE_REGULAR_EXPRESSION; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (strings, matcher, retraceBase) -> { |
| if (matcher.start(captureGroup) == NO_MATCH) { |
| return strings; |
| } |
| String typeName = matcher.group(captureGroup); |
| String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName); |
| if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) { |
| return strings; |
| } |
| TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor); |
| List<RetraceString> retracedStrings = new ArrayList<>(); |
| RetraceTypeResult retracedType = retraceBase.retrace(typeReference); |
| for (RetraceString retraceString : strings) { |
| retracedType.forEach( |
| element -> { |
| TypeReference retracedReference = element.getTypeReference(); |
| retracedStrings.add( |
| retraceString |
| .transform() |
| .setTypeOrReturnTypeContext(retracedReference) |
| .replaceInString( |
| retracedReference == null ? "void" : retracedReference.getTypeName(), |
| matcher.start(captureGroup), |
| matcher.end(captureGroup)) |
| .build()); |
| }); |
| } |
| return retracedStrings; |
| }; |
| } |
| } |
| |
| private class MethodArgumentsGroup extends RegularExpressionGroup { |
| |
| @Override |
| String shortName() { |
| return "%a"; |
| } |
| |
| @Override |
| String subExpression() { |
| return "((" + JAVA_TYPE_REGULAR_EXPRESSION + "\\,)*" + JAVA_TYPE_REGULAR_EXPRESSION + ")?"; |
| } |
| |
| @Override |
| RegularExpressionGroupHandler createHandler(String captureGroup) { |
| return (strings, matcher, retraceBase) -> { |
| if (matcher.start(captureGroup) == NO_MATCH) { |
| return strings; |
| } |
| Set<List<TypeReference>> initialValue = new LinkedHashSet<>(); |
| initialValue.add(new ArrayList<>()); |
| Set<List<TypeReference>> allRetracedReferences = |
| Arrays.stream(matcher.group(captureGroup).split(",")) |
| .map(String::trim) |
| .reduce( |
| initialValue, |
| (acc, typeName) -> { |
| String descriptor = DescriptorUtils.javaTypeToDescriptor(typeName); |
| if (!DescriptorUtils.isDescriptor(descriptor) && !"V".equals(descriptor)) { |
| return acc; |
| } |
| TypeReference typeReference = Reference.returnTypeFromDescriptor(descriptor); |
| Set<List<TypeReference>> retracedTypes = new LinkedHashSet<>(); |
| retraceBase |
| .retrace(typeReference) |
| .forEach( |
| element -> { |
| for (List<TypeReference> currentReferences : acc) { |
| ArrayList<TypeReference> newList = |
| new ArrayList<>(currentReferences); |
| newList.add(element.getTypeReference()); |
| retracedTypes.add(newList); |
| } |
| }); |
| return retracedTypes; |
| }, |
| (l1, l2) -> { |
| l1.addAll(l2); |
| return l1; |
| }); |
| List<RetraceString> retracedStrings = new ArrayList<>(); |
| for (RetraceString retraceString : strings) { |
| if (retraceString.getMethodContext() != null |
| && !allRetracedReferences.contains( |
| retraceString.getMethodContext().getMethodReference().getFormalTypes())) { |
| // Prune the string since we now know the formals. |
| String formals = |
| retraceString.getMethodContext().getMethodReference().getFormalTypes().stream() |
| .map(TypeReference::getTypeName) |
| .collect(Collectors.joining(",")); |
| diagnosticsHandler.info( |
| new StringDiagnostic( |
| "Pruning " |
| + retraceString.getRetracedString() |
| + " from result because formals (" |
| + formals |
| + ") do not match result set.")); |
| continue; |
| } |
| for (List<TypeReference> retracedReferences : allRetracedReferences) { |
| retracedStrings.add( |
| retraceString |
| .transform() |
| .replaceInString( |
| retracedReferences.stream() |
| .map(TypeReference::getTypeName) |
| .collect(Collectors.joining(",")), |
| matcher.start(captureGroup), |
| matcher.end(captureGroup)) |
| .build()); |
| } |
| } |
| return retracedStrings; |
| }; |
| } |
| } |
| } |