2
* ProGuard -- shrinking, optimization, obfuscation, and preverification
5
* Copyright (c) 2002-2015 Eric Lafortune @ GuardSquare
7
* This program is free software; you can redistribute it and/or modify it
8
* under the terms of the GNU General Public License as published by the Free
9
* Software Foundation; either version 2 of the License, or (at your option)
12
* This program is distributed in the hope that it will be useful, but WITHOUT
13
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17
* You should have received a copy of the GNU General Public License along
18
* with this program; if not, write to the Free Software Foundation, Inc.,
19
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
package proguard.retrace;
23
import proguard.obfuscate.MappingProcessor;
28
* This class accumulates mapping information and then transforms stack frames
31
* @author Eric Lafortune
33
public class FrameRemapper implements MappingProcessor
35
private final Map classMap = new HashMap();
36
private final Map classFieldMap = new HashMap();
37
private final Map classMethodMap = new HashMap();
41
* Transforms the given obfuscated frame back to one or more original frames.
43
public List transform(FrameInfo obfuscatedFrame)
45
// First remap the class name.
46
String originalClassName = originalClassName(obfuscatedFrame.getClassName());
47
if (originalClassName == null)
52
List originalFrames = new ArrayList();
54
// Create any transformed frames with remapped field names.
55
transformFieldInfo(obfuscatedFrame,
59
// Create any transformed frames with remapped method names.
60
transformMethodInfo(obfuscatedFrame,
64
if (originalFrames.isEmpty())
66
// Create a transformed frame with the remapped class name.
67
originalFrames.add(new FrameInfo(originalClassName,
68
sourceFileName(originalClassName),
69
obfuscatedFrame.getLineNumber(),
70
obfuscatedFrame.getType(),
71
obfuscatedFrame.getFieldName(),
72
obfuscatedFrame.getMethodName(),
73
obfuscatedFrame.getArguments()));
76
return originalFrames;
81
* Transforms the obfuscated frame into one or more original frames,
82
* if the frame contains information about a field that can be remapped.
83
* @param obfuscatedFrame the obfuscated frame.
84
* @param originalFieldFrames the list in which remapped frames can be
87
private void transformFieldInfo(FrameInfo obfuscatedFrame,
88
String originalClassName,
89
List originalFieldFrames)
91
// Class name -> obfuscated field names.
92
Map fieldMap = (Map)classFieldMap.get(originalClassName);
95
// Obfuscated field names -> fields.
96
String obfuscatedFieldName = obfuscatedFrame.getFieldName();
97
List fieldList = (List)fieldMap.get(obfuscatedFieldName);
98
if (fieldList != null)
100
String obfuscatedType = obfuscatedFrame.getType();
101
String originalType = obfuscatedType == null ? null :
102
originalType(obfuscatedType);
104
// Find all matching fields.
105
Iterator fieldInfoIterator = fieldList.iterator();
106
while (fieldInfoIterator.hasNext())
108
FieldInfo fieldInfo = (FieldInfo)fieldInfoIterator.next();
109
if (fieldInfo.matches(originalType))
111
originalFieldFrames.add(new FrameInfo(fieldInfo.originalClassName,
112
sourceFileName(fieldInfo.originalClassName),
113
obfuscatedFrame.getLineNumber(),
114
fieldInfo.originalType,
115
fieldInfo.originalName,
116
obfuscatedFrame.getMethodName(),
117
obfuscatedFrame.getArguments()));
126
* Transforms the obfuscated frame into one or more original frames,
127
* if the frame contains information about a method that can be remapped.
128
* @param obfuscatedFrame the obfuscated frame.
129
* @param originalMethodFrames the list in which remapped frames can be
132
private void transformMethodInfo(FrameInfo obfuscatedFrame,
133
String originalClassName,
134
List originalMethodFrames)
136
// Class name -> obfuscated method names.
137
Map methodMap = (Map)classMethodMap.get(originalClassName);
138
if (methodMap != null)
140
// Obfuscated method names -> methods.
141
String obfuscatedMethodName = obfuscatedFrame.getMethodName();
142
Set methodList = (Set)methodMap.get(obfuscatedMethodName);
143
if (methodList != null)
145
int obfuscatedLineNumber = obfuscatedFrame.getLineNumber();
147
String obfuscatedType = obfuscatedFrame.getType();
148
String originalType = obfuscatedType == null ? null :
149
originalType(obfuscatedType);
151
String obfuscatedArguments = obfuscatedFrame.getArguments();
152
String originalArguments = obfuscatedArguments == null ? null :
153
originalArguments(obfuscatedArguments);
155
// Find all matching methods.
156
Iterator methodInfoIterator = methodList.iterator();
157
while (methodInfoIterator.hasNext())
159
MethodInfo methodInfo = (MethodInfo)methodInfoIterator.next();
160
if (methodInfo.matches(obfuscatedLineNumber,
164
// Do we have a different original first line number?
165
// We're allowing unknown values, represented as 0.
166
int lineNumber = obfuscatedFrame.getLineNumber();
167
if (methodInfo.originalFirstLineNumber != methodInfo.obfuscatedFirstLineNumber)
169
// Do we have an original line number range and
170
// sufficient information to shift the line number?
171
lineNumber = methodInfo.originalLastLineNumber != 0 &&
172
methodInfo.originalLastLineNumber != methodInfo.originalFirstLineNumber &&
173
methodInfo.obfuscatedFirstLineNumber != 0 &&
175
methodInfo.originalFirstLineNumber - methodInfo.obfuscatedFirstLineNumber + lineNumber :
176
methodInfo.originalFirstLineNumber;
179
originalMethodFrames.add(new FrameInfo(methodInfo.originalClassName,
180
sourceFileName(methodInfo.originalClassName),
182
methodInfo.originalType,
183
obfuscatedFrame.getFieldName(),
184
methodInfo.originalName,
185
methodInfo.originalArguments));
194
* Returns the original argument types.
196
private String originalArguments(String obfuscatedArguments)
198
StringBuffer originalArguments = new StringBuffer();
203
int endIndex = obfuscatedArguments.indexOf(',', startIndex);
209
originalArguments.append(originalType(obfuscatedArguments.substring(startIndex, endIndex).trim())).append(',');
211
startIndex = endIndex + 1;
214
originalArguments.append(originalType(obfuscatedArguments.substring(startIndex).trim()));
216
return originalArguments.toString();
221
* Returns the original type.
223
private String originalType(String obfuscatedType)
225
int index = obfuscatedType.indexOf('[');
228
originalClassName(obfuscatedType.substring(0, index)) + obfuscatedType.substring(index) :
229
originalClassName(obfuscatedType);
234
* Returns the original class name.
236
private String originalClassName(String obfuscatedClassName)
238
String originalClassName = (String)classMap.get(obfuscatedClassName);
240
return originalClassName != null ?
247
* Returns the Java source file name that typically corresponds to the
250
private String sourceFileName(String className)
252
int index1 = className.lastIndexOf('.') + 1;
253
int index2 = className.indexOf('$', index1);
256
className.substring(index1, index2) :
257
className.substring(index1)) +
262
// Implementations for MappingProcessor.
264
public boolean processClassMapping(String className,
267
// Obfuscated class name -> original class name.
268
classMap.put(newClassName, className);
274
public void processFieldMapping(String className,
280
// Obfuscated class name -> obfuscated field names.
281
Map fieldMap = (Map)classFieldMap.get(newClassName);
282
if (fieldMap == null)
284
fieldMap = new HashMap();
285
classFieldMap.put(newClassName, fieldMap);
288
// Obfuscated field name -> fields.
289
Set fieldList = (Set)fieldMap.get(newFieldName);
290
if (fieldList == null)
292
fieldList = new LinkedHashSet();
293
fieldMap.put(newFieldName, fieldList);
296
// Add the field information.
297
fieldList.add(new FieldInfo(className,
303
public void processMethodMapping(String className,
306
String methodReturnType,
308
String methodArguments,
310
int newFirstLineNumber,
311
int newLastLineNumber,
312
String newMethodName)
314
// Original class name -> obfuscated method names.
315
Map methodMap = (Map)classMethodMap.get(newClassName);
316
if (methodMap == null)
318
methodMap = new HashMap();
319
classMethodMap.put(newClassName, methodMap);
322
// Obfuscated method name -> methods.
323
Set methodList = (Set)methodMap.get(newMethodName);
324
if (methodList == null)
326
methodList = new LinkedHashSet();
327
methodMap.put(newMethodName, methodList);
330
// Add the method information.
331
methodList.add(new MethodInfo(newFirstLineNumber,
343
* Information about the original version and the obfuscated version of
344
* a field (without the obfuscated class name or field name).
346
private static class FieldInfo
348
private final String originalClassName;
349
private final String originalType;
350
private final String originalName;
354
* Creates a new FieldInfo with the given properties.
356
private FieldInfo(String originalClassName,
360
this.originalClassName = originalClassName;
361
this.originalType = originalType;
362
this.originalName = originalName;
367
* Returns whether the given type matches the original type of this field.
368
* The given type may be a null wildcard.
370
private boolean matches(String originalType)
373
originalType == null || originalType.equals(this.originalType);
379
* Information about the original version and the obfuscated version of
380
* a method (without the obfuscated class name or method name).
382
private static class MethodInfo
384
private final int obfuscatedFirstLineNumber;
385
private final int obfuscatedLastLineNumber;
386
private final String originalClassName;
387
private final int originalFirstLineNumber;
388
private final int originalLastLineNumber;
389
private final String originalType;
390
private final String originalName;
391
private final String originalArguments;
395
* Creates a new MethodInfo with the given properties.
397
private MethodInfo(int obfuscatedFirstLineNumber,
398
int obfuscatedLastLineNumber,
399
String originalClassName,
400
int originalFirstLineNumber,
401
int originalLastLineNumber,
404
String originalArguments)
406
this.obfuscatedFirstLineNumber = obfuscatedFirstLineNumber;
407
this.obfuscatedLastLineNumber = obfuscatedLastLineNumber;
408
this.originalType = originalType;
409
this.originalArguments = originalArguments;
410
this.originalClassName = originalClassName;
411
this.originalName = originalName;
412
this.originalFirstLineNumber = originalFirstLineNumber;
413
this.originalLastLineNumber = originalLastLineNumber;
418
* Returns whether the given properties match the properties of this
419
* method. The given properties may be null wildcards.
421
private boolean matches(int obfuscatedLineNumber,
423
String originalArguments)
426
(obfuscatedLineNumber == 0 ? obfuscatedLastLineNumber == 0 :
427
obfuscatedFirstLineNumber <= obfuscatedLineNumber && obfuscatedLineNumber <= obfuscatedLastLineNumber) &&
428
(originalType == null || originalType.equals(this.originalType)) &&
429
(originalArguments == null || originalArguments.equals(this.originalArguments));