blob: 083a29e9d1e686d40d6cf914bddcb94153b289b1 [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.obfuscate.MappingProcessor;
import java.util.*;
/**
* This class accumulates mapping information and then transforms stack frames
* accordingly.
*
* @author Eric Lafortune
*/
public class FrameRemapper implements MappingProcessor
{
// Obfuscated class name -> original class name.
private final Map<String,String> classMap = new HashMap<String,String>();
// Original class name -> obfuscated member name -> member info set.
private final Map<String,Map<String,Set<FieldInfo>>> classFieldMap = new HashMap<String,Map<String,Set<FieldInfo>>>();
private final Map<String,Map<String,Set<MethodInfo>>> classMethodMap = new HashMap<String,Map<String,Set<MethodInfo>>>();
/**
* Transforms the given obfuscated frame back to one or more original frames.
*/
public List<FrameInfo> transform(FrameInfo obfuscatedFrame)
{
// First remap the class name.
String originalClassName = originalClassName(obfuscatedFrame.getClassName());
if (originalClassName == null)
{
return null;
}
List<FrameInfo> originalFrames = new ArrayList<FrameInfo>();
// Create any transformed frames with remapped field names.
transformFieldInfo(obfuscatedFrame,
originalClassName,
originalFrames);
// Create any transformed frames with remapped method names.
transformMethodInfo(obfuscatedFrame,
originalClassName,
originalFrames);
if (originalFrames.isEmpty())
{
// Create a transformed frame with the remapped class name.
originalFrames.add(new FrameInfo(originalClassName,
sourceFileName(originalClassName),
obfuscatedFrame.getLineNumber(),
obfuscatedFrame.getType(),
obfuscatedFrame.getFieldName(),
obfuscatedFrame.getMethodName(),
obfuscatedFrame.getArguments()));
}
return originalFrames;
}
/**
* Transforms the obfuscated frame into one or more original frames,
* if the frame contains information about a field that can be remapped.
* @param obfuscatedFrame the obfuscated frame.
* @param originalFieldFrames the list in which remapped frames can be
* collected.
*/
private void transformFieldInfo(FrameInfo obfuscatedFrame,
String originalClassName,
List<FrameInfo> originalFieldFrames)
{
// Class name -> obfuscated field names.
Map<String,Set<FieldInfo>> fieldMap = classFieldMap.get(originalClassName);
if (fieldMap != null)
{
// Obfuscated field names -> fields.
String obfuscatedFieldName = obfuscatedFrame.getFieldName();
Set<FieldInfo> fieldSet = fieldMap.get(obfuscatedFieldName);
if (fieldSet != null)
{
String obfuscatedType = obfuscatedFrame.getType();
String originalType = obfuscatedType == null ? null :
originalType(obfuscatedType);
// Find all matching fields.
Iterator<FieldInfo> fieldInfoIterator = fieldSet.iterator();
while (fieldInfoIterator.hasNext())
{
FieldInfo fieldInfo = fieldInfoIterator.next();
if (fieldInfo.matches(originalType))
{
originalFieldFrames.add(new FrameInfo(fieldInfo.originalClassName,
sourceFileName(fieldInfo.originalClassName),
obfuscatedFrame.getLineNumber(),
fieldInfo.originalType,
fieldInfo.originalName,
obfuscatedFrame.getMethodName(),
obfuscatedFrame.getArguments()));
}
}
}
}
}
/**
* Transforms the obfuscated frame into one or more original frames,
* if the frame contains information about a method that can be remapped.
* @param obfuscatedFrame the obfuscated frame.
* @param originalMethodFrames the list in which remapped frames can be
* collected.
*/
private void transformMethodInfo(FrameInfo obfuscatedFrame,
String originalClassName,
List<FrameInfo> originalMethodFrames)
{
// Class name -> obfuscated method names.
Map<String,Set<MethodInfo>> methodMap = classMethodMap.get(originalClassName);
if (methodMap != null)
{
// Obfuscated method names -> methods.
String obfuscatedMethodName = obfuscatedFrame.getMethodName();
Set<MethodInfo> methodSet = methodMap.get(obfuscatedMethodName);
if (methodSet != null)
{
int obfuscatedLineNumber = obfuscatedFrame.getLineNumber();
String obfuscatedType = obfuscatedFrame.getType();
String originalType = obfuscatedType == null ? null :
originalType(obfuscatedType);
String obfuscatedArguments = obfuscatedFrame.getArguments();
String originalArguments = obfuscatedArguments == null ? null :
originalArguments(obfuscatedArguments);
// Find all matching methods.
Iterator<MethodInfo> methodInfoIterator = methodSet.iterator();
while (methodInfoIterator.hasNext())
{
MethodInfo methodInfo = methodInfoIterator.next();
if (methodInfo.matches(obfuscatedLineNumber,
originalType,
originalArguments))
{
// Do we have a different original first line number?
// We're allowing unknown values, represented as 0.
int lineNumber = obfuscatedFrame.getLineNumber();
if (methodInfo.originalFirstLineNumber != methodInfo.obfuscatedFirstLineNumber)
{
// Do we have an original line number range and
// sufficient information to shift the line number?
lineNumber = methodInfo.originalLastLineNumber != 0 &&
methodInfo.originalLastLineNumber != methodInfo.originalFirstLineNumber &&
methodInfo.obfuscatedFirstLineNumber != 0 &&
lineNumber != 0 ?
methodInfo.originalFirstLineNumber - methodInfo.obfuscatedFirstLineNumber + lineNumber :
methodInfo.originalFirstLineNumber;
}
originalMethodFrames.add(new FrameInfo(methodInfo.originalClassName,
sourceFileName(methodInfo.originalClassName),
lineNumber,
methodInfo.originalType,
obfuscatedFrame.getFieldName(),
methodInfo.originalName,
methodInfo.originalArguments));
}
}
}
}
}
/**
* Returns the original argument types.
*/
private String originalArguments(String obfuscatedArguments)
{
StringBuilder originalArguments = new StringBuilder();
int startIndex = 0;
while (true)
{
int endIndex = obfuscatedArguments.indexOf(',', startIndex);
if (endIndex < 0)
{
break;
}
originalArguments.append(originalType(obfuscatedArguments.substring(startIndex, endIndex).trim())).append(',');
startIndex = endIndex + 1;
}
originalArguments.append(originalType(obfuscatedArguments.substring(startIndex).trim()));
return originalArguments.toString();
}
/**
* Returns the original type.
*/
private String originalType(String obfuscatedType)
{
int index = obfuscatedType.indexOf('[');
return index >= 0 ?
originalClassName(obfuscatedType.substring(0, index)) + obfuscatedType.substring(index) :
originalClassName(obfuscatedType);
}
/**
* Returns the original class name.
*/
private String originalClassName(String obfuscatedClassName)
{
String originalClassName = classMap.get(obfuscatedClassName);
return originalClassName != null ?
originalClassName :
obfuscatedClassName;
}
/**
* Returns the Java source file name that typically corresponds to the
* given class name.
*/
private String sourceFileName(String className)
{
int index1 = className.lastIndexOf('.') + 1;
int index2 = className.indexOf('$', index1);
return (index2 > 0 ?
className.substring(index1, index2) :
className.substring(index1)) +
".java";
}
// Implementations for MappingProcessor.
public boolean processClassMapping(String className,
String newClassName)
{
// Obfuscated class name -> original class name.
classMap.put(newClassName, className);
return true;
}
public void processFieldMapping(String className,
String fieldType,
String fieldName,
String newClassName,
String newFieldName)
{
// Obfuscated class name -> obfuscated field names.
Map<String,Set<FieldInfo>> fieldMap = classFieldMap.get(newClassName);
if (fieldMap == null)
{
fieldMap = new HashMap<String,Set<FieldInfo>>();
classFieldMap.put(newClassName, fieldMap);
}
// Obfuscated field name -> fields.
Set<FieldInfo> fieldSet = fieldMap.get(newFieldName);
if (fieldSet == null)
{
fieldSet = new LinkedHashSet<FieldInfo>();
fieldMap.put(newFieldName, fieldSet);
}
// Add the field information.
fieldSet.add(new FieldInfo(className,
fieldType,
fieldName));
}
public void processMethodMapping(String className,
int firstLineNumber,
int lastLineNumber,
String methodReturnType,
String methodName,
String methodArguments,
String newClassName,
int newFirstLineNumber,
int newLastLineNumber,
String newMethodName)
{
// Original class name -> obfuscated method names.
Map<String,Set<MethodInfo>> methodMap = classMethodMap.get(newClassName);
if (methodMap == null)
{
methodMap = new HashMap<String,Set<MethodInfo>>();
classMethodMap.put(newClassName, methodMap);
}
// Obfuscated method name -> methods.
Set<MethodInfo> methodSet = methodMap.get(newMethodName);
if (methodSet == null)
{
methodSet = new LinkedHashSet<MethodInfo>();
methodMap.put(newMethodName, methodSet);
}
// Add the method information.
methodSet.add(new MethodInfo(newFirstLineNumber,
newLastLineNumber,
className,
firstLineNumber,
lastLineNumber,
methodReturnType,
methodName,
methodArguments));
}
/**
* Information about the original version and the obfuscated version of
* a field (without the obfuscated class name or field name).
*/
private static class FieldInfo
{
private final String originalClassName;
private final String originalType;
private final String originalName;
/**
* Creates a new FieldInfo with the given properties.
*/
private FieldInfo(String originalClassName,
String originalType,
String originalName)
{
this.originalClassName = originalClassName;
this.originalType = originalType;
this.originalName = originalName;
}
/**
* Returns whether the given type matches the original type of this field.
* The given type may be a null wildcard.
*/
private boolean matches(String originalType)
{
return
originalType == null || originalType.equals(this.originalType);
}
}
/**
* Information about the original version and the obfuscated version of
* a method (without the obfuscated class name or method name).
*/
private static class MethodInfo
{
private final int obfuscatedFirstLineNumber;
private final int obfuscatedLastLineNumber;
private final String originalClassName;
private final int originalFirstLineNumber;
private final int originalLastLineNumber;
private final String originalType;
private final String originalName;
private final String originalArguments;
/**
* Creates a new MethodInfo with the given properties.
*/
private MethodInfo(int obfuscatedFirstLineNumber,
int obfuscatedLastLineNumber,
String originalClassName,
int originalFirstLineNumber,
int originalLastLineNumber,
String originalType,
String originalName,
String originalArguments)
{
this.obfuscatedFirstLineNumber = obfuscatedFirstLineNumber;
this.obfuscatedLastLineNumber = obfuscatedLastLineNumber;
this.originalType = originalType;
this.originalArguments = originalArguments;
this.originalClassName = originalClassName;
this.originalName = originalName;
this.originalFirstLineNumber = originalFirstLineNumber;
this.originalLastLineNumber = originalLastLineNumber;
}
/**
* Returns whether the given properties match the properties of this
* method. The given properties may be null wildcards.
*/
private boolean matches(int obfuscatedLineNumber,
String originalType,
String originalArguments)
{
return
(obfuscatedLineNumber == 0 ? obfuscatedLastLineNumber == 0 :
obfuscatedFirstLineNumber <= obfuscatedLineNumber && obfuscatedLineNumber <= obfuscatedLastLineNumber) &&
(originalType == null || originalType.equals(this.originalType)) &&
(originalArguments == null || originalArguments.equals(this.originalArguments));
}
}
}