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