| /* |
| * ProGuard -- shrinking, optimization, obfuscation, and preverification |
| * of Java bytecode. |
| * |
| * Copyright (c) 2002-2017 Eric Lafortune @ GuardSquare |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the Free |
| * Software Foundation; either version 2 of the License, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| package proguard.retrace; |
| |
| import proguard.classfile.util.ClassUtil; |
| |
| import java.util.regex.*; |
| |
| /** |
| * This class can parse and format lines that represent stack frames |
| * matching a given regular expression. |
| * |
| * @author Eric Lafortune |
| */ |
| public class FramePattern |
| { |
| // The pattern matcher has problems with \\b against some unicode |
| // characters, so we're no longer using \\b for classes and class members. |
| private static final String REGEX_CLASS = "(?:[^\\s\":./()]+\\.)*[^\\s\":./()]+"; |
| private static final String REGEX_CLASS_SLASH = "(?:[^\\s\":./()]+/)*[^\\s\":./()]+"; |
| private static final String REGEX_SOURCE_FILE = "[^:()]*"; |
| private static final String REGEX_LINE_NUMBER = "-?\\b\\d+\\b"; |
| private static final String REGEX_TYPE = REGEX_CLASS + "(?:\\[\\])*"; |
| private static final String REGEX_MEMBER = "<?[^\\s\":./()]+>?"; |
| private static final String REGEX_ARGUMENTS = "(?:" + REGEX_TYPE + "(?:\\s*,\\s*" + REGEX_TYPE + ")*)?"; |
| |
| private final char[] expressionTypes = new char[32]; |
| private final int expressionTypeCount; |
| private final Pattern pattern; |
| private final boolean verbose; |
| |
| |
| /** |
| * Creates a new FramePattern. |
| */ |
| public FramePattern(String regularExpression, boolean verbose) |
| { |
| // Construct the regular expression. |
| StringBuffer expressionBuffer = new StringBuffer(regularExpression.length() + 32); |
| |
| int expressionTypeCount = 0; |
| int index = 0; |
| while (true) |
| { |
| int nextIndex = regularExpression.indexOf('%', index); |
| if (nextIndex < 0 || |
| nextIndex == regularExpression.length()-1 || |
| expressionTypeCount == expressionTypes.length) |
| { |
| break; |
| } |
| |
| // Copy a literal piece of the input line. |
| expressionBuffer.append(regularExpression.substring(index, nextIndex)); |
| expressionBuffer.append('('); |
| |
| char expressionType = regularExpression.charAt(nextIndex + 1); |
| switch(expressionType) |
| { |
| case 'c': |
| expressionBuffer.append(REGEX_CLASS); |
| break; |
| |
| case 'C': |
| expressionBuffer.append(REGEX_CLASS_SLASH); |
| break; |
| |
| case 's': |
| expressionBuffer.append(REGEX_SOURCE_FILE); |
| break; |
| |
| case 'l': |
| expressionBuffer.append(REGEX_LINE_NUMBER); |
| break; |
| |
| case 't': |
| expressionBuffer.append(REGEX_TYPE); |
| break; |
| |
| case 'f': |
| expressionBuffer.append(REGEX_MEMBER); |
| break; |
| |
| case 'm': |
| expressionBuffer.append(REGEX_MEMBER); |
| break; |
| |
| case 'a': |
| expressionBuffer.append(REGEX_ARGUMENTS); |
| break; |
| } |
| |
| expressionBuffer.append(')'); |
| |
| expressionTypes[expressionTypeCount++] = expressionType; |
| |
| index = nextIndex + 2; |
| } |
| |
| // Copy the last literal piece of the input line. |
| expressionBuffer.append(regularExpression.substring(index)); |
| |
| this.expressionTypeCount = expressionTypeCount; |
| this.pattern = Pattern.compile(expressionBuffer.toString()); |
| this.verbose = verbose; |
| } |
| |
| |
| /** |
| * Parses all frame information from a given line. |
| * @param line a line that represents a stack frame. |
| * @return the parsed information, or null if the line doesn't match a |
| * stack frame. |
| */ |
| public FrameInfo parse(String line) |
| { |
| // Try to match it against the regular expression. |
| Matcher matcher = pattern.matcher(line); |
| |
| if (!matcher.matches()) |
| { |
| return null; |
| } |
| |
| // The line matched the regular expression. |
| String className = null; |
| String sourceFile = null; |
| int lineNumber = 0; |
| String type = null; |
| String fieldName = null; |
| String methodName = null; |
| String arguments = null; |
| |
| // Extract a class name, a line number, a type, and |
| // arguments. |
| for (int expressionTypeIndex = 0; expressionTypeIndex < expressionTypeCount; expressionTypeIndex++) |
| { |
| int startIndex = matcher.start(expressionTypeIndex + 1); |
| if (startIndex >= 0) |
| { |
| String match = matcher.group(expressionTypeIndex + 1); |
| |
| char expressionType = expressionTypes[expressionTypeIndex]; |
| switch (expressionType) |
| { |
| case 'c': |
| className = match; |
| break; |
| |
| case 'C': |
| className = ClassUtil.externalClassName(match); |
| break; |
| |
| case 's': |
| sourceFile = match; |
| break; |
| |
| case 'l': |
| lineNumber = Integer.parseInt(match); |
| break; |
| |
| case 't': |
| type = match; |
| break; |
| |
| case 'f': |
| fieldName = match; |
| break; |
| |
| case 'm': |
| methodName = match; |
| break; |
| |
| case 'a': |
| arguments = match; |
| break; |
| } |
| } |
| } |
| |
| return new FrameInfo(className, |
| sourceFile, |
| lineNumber, |
| type, |
| fieldName, |
| methodName, |
| arguments); |
| } |
| |
| |
| /** |
| * Formats the given frame information based on the given template line. |
| * It is the reverse of {@link #parse(String)}, but optionally with |
| * different frame information. |
| * @param line a template line that represents a stack frame. |
| * @param frameInfo information about a stack frame. |
| * @return the formatted line, or null if the line doesn't match a |
| * stack frame. |
| */ |
| public String format(String line, FrameInfo frameInfo) |
| { |
| // Try to match it against the regular expression. |
| Matcher matcher = pattern.matcher(line); |
| |
| if (!matcher.matches()) |
| { |
| return null; |
| } |
| |
| StringBuffer formattedBuffer = new StringBuffer(); |
| |
| int lineIndex = 0; |
| for (int expressionTypeIndex = 0; expressionTypeIndex < expressionTypeCount; expressionTypeIndex++) |
| { |
| int startIndex = matcher.start(expressionTypeIndex + 1); |
| if (startIndex >= 0) |
| { |
| int endIndex = matcher.end(expressionTypeIndex + 1); |
| String match = matcher.group(expressionTypeIndex + 1); |
| |
| // Copy a literal piece of the input line. |
| formattedBuffer.append(line.substring(lineIndex, startIndex)); |
| |
| // Copy a matched and translated piece of the input line. |
| char expressionType = expressionTypes[expressionTypeIndex]; |
| switch (expressionType) |
| { |
| case 'c': |
| formattedBuffer.append(frameInfo.getClassName()); |
| break; |
| |
| case 'C': |
| formattedBuffer.append(ClassUtil.internalClassName(frameInfo.getClassName())); |
| break; |
| |
| case 's': |
| formattedBuffer.append(frameInfo.getSourceFile()); |
| break; |
| |
| case 'l': |
| formattedBuffer.append(frameInfo.getLineNumber()); |
| break; |
| |
| case 't': |
| formattedBuffer.append(frameInfo.getType()); |
| break; |
| |
| case 'f': |
| if (verbose) |
| { |
| formattedBuffer.append(frameInfo.getType()).append(' '); |
| } |
| formattedBuffer.append(frameInfo.getFieldName()); |
| break; |
| |
| case 'm': |
| if (verbose) |
| { |
| formattedBuffer.append(frameInfo.getType()).append(' '); |
| } |
| formattedBuffer.append(frameInfo.getMethodName()); |
| if (verbose) |
| { |
| formattedBuffer.append('(').append(frameInfo.getArguments()).append(')'); |
| } |
| break; |
| |
| case 'a': |
| formattedBuffer.append(frameInfo.getArguments()); |
| break; |
| } |
| |
| // Skip the original element whose replacement value |
| // has just been appended. |
| lineIndex = endIndex; |
| } |
| } |
| |
| // Copy the last literal piece of the input line. |
| formattedBuffer.append(line.substring(lineIndex)); |
| |
| // Return the formatted line. |
| return formattedBuffer.toString(); |
| } |
| } |