/* * 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)); } } }