blob: e3eab1256945d43db05302938d82f5d7d5b5391e [file] [log] [blame]
/*
* 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();
}
}