blob: 1fff4daa7b6cb7311a04a778fc56ef4fc507bd28 [file] [log] [blame]
/*
* ProGuard -- shrinking, optimization, obfuscation, and preverification
* of Java bytecode.
*
* Copyright (c) 2002-2019 Guardsquare NV
*
* 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.obfuscate;
import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.util.*;
import proguard.classfile.visitor.*;
import proguard.optimize.peephole.LineNumberLinearizer;
import java.io.*;
import java.util.Stack;
/**
* This ClassVisitor prints out the renamed classes and class members with
* their old names and new names.
*
* @see ClassRenamer
*
* @author Eric Lafortune
*/
public class MappingPrinter
extends SimplifiedVisitor
implements ClassVisitor,
MemberVisitor,
AttributeVisitor
{
private final PrintWriter pw;
// A field serving as a return value for the visitor methods.
private boolean printed;
/**
* Creates a new MappingPrinter that prints to the given writer.
* @param printWriter the writer to which to print.
*/
public MappingPrinter(PrintWriter printWriter)
{
this.pw = printWriter;
}
// Implementations for ClassVisitor.
public void visitProgramClass(ProgramClass programClass)
{
String name = programClass.getName();
String newName = ClassObfuscator.newClassName(programClass);
// Print out the class mapping.
pw.println(ClassUtil.externalClassName(name) +
" -> " +
ClassUtil.externalClassName(newName) +
":");
// Print out the class members.
programClass.fieldsAccept(this);
programClass.methodsAccept(this);
}
// Implementations for MemberVisitor.
public void visitProgramField(ProgramClass programClass, ProgramField programField)
{
String fieldName = programField.getName(programClass);
String obfuscatedFieldName = MemberObfuscator.newMemberName(programField);
if (obfuscatedFieldName == null)
{
obfuscatedFieldName = fieldName;
}
// Print out the field mapping.
pw.println(" " +
ClassUtil.externalType(programField.getDescriptor(programClass)) + " " +
fieldName +
" -> " +
obfuscatedFieldName);
}
public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod)
{
String methodName = programMethod.getName(programClass);
String obfuscatedMethodName = MemberObfuscator.newMemberName(programMethod);
if (obfuscatedMethodName == null)
{
obfuscatedMethodName = methodName;
}
// Print out the method mapping, if it has line numbers.
printed = false;
programMethod.attributesAccept(programClass, this);
// Otherwise print out the method mapping without line numbers.
if (!printed)
{
pw.println(" " +
ClassUtil.externalMethodReturnType(programMethod.getDescriptor(programClass)) + " " +
methodName + JavaConstants.METHOD_ARGUMENTS_OPEN +
ClassUtil.externalMethodArguments(programMethod.getDescriptor(programClass)) + JavaConstants.METHOD_ARGUMENTS_CLOSE +
" -> " +
obfuscatedMethodName);
}
}
// Implementations for AttributeVisitor.
public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
codeAttribute.attributesAccept(clazz, method, this);
}
public void visitLineNumberTableAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute, LineNumberTableAttribute lineNumberTableAttribute)
{
LineNumberInfo[] lineNumberTable = lineNumberTableAttribute.lineNumberTable;
int lineNumberTableLength = lineNumberTableAttribute.u2lineNumberTableLength;
String methodName = method.getName(clazz);
String methodDescriptor = method.getDescriptor(clazz);
String obfuscatedMethodName = MemberObfuscator.newMemberName(method);
if (obfuscatedMethodName == null)
{
obfuscatedMethodName = methodName;
}
int lowestLineNumber = lineNumberTableAttribute.getLowestLineNumber();
int highestLineNumber = lineNumberTableAttribute.getHighestLineNumber();
// Does the method have any local line numbers at all?
if (lineNumberTableAttribute.getSource(codeAttribute.u4codeLength) == null)
{
if (lowestLineNumber > 0)
{
// Print out the line number range of the method,
// ignoring line numbers of any inlined methods.
pw.println(" " +
lowestLineNumber + ":" +
highestLineNumber + ":" +
ClassUtil.externalMethodReturnType(method.getDescriptor(clazz)) + " " +
methodName + JavaConstants.METHOD_ARGUMENTS_OPEN +
ClassUtil.externalMethodArguments(method.getDescriptor(clazz)) + JavaConstants.METHOD_ARGUMENTS_CLOSE +
" -> " +
obfuscatedMethodName);
}
else
{
// Print out the method mapping without line numbers.
pw.println(" " +
ClassUtil.externalMethodReturnType(method.getDescriptor(clazz)) + " " +
methodName + JavaConstants.METHOD_ARGUMENTS_OPEN +
ClassUtil.externalMethodArguments(method.getDescriptor(clazz)) + JavaConstants.METHOD_ARGUMENTS_CLOSE +
" -> " +
obfuscatedMethodName);
}
}
// Print out the line numbers of any inlined methods and their
// enclosing methods.
Stack enclosingLineNumbers = new Stack();
LineNumberInfo previousInfo = new LineNumberInfo(0, 0);
for (int index = 0; index < lineNumberTableLength; index++)
{
LineNumberInfo info = lineNumberTable[index];
// Are we entering or exiting an inlined block (or a merged block)?
// We're testing on the identities out of convenience.
String previousSource = previousInfo.getSource();
String source = info.getSource();
// Source can be null for injected code.
if (source != null && source != previousSource)
{
// Are we entering or exiting the block?
int previousLineNumber = previousInfo.u2lineNumber;
int lineNumber = info.u2lineNumber;
if (lineNumber > previousLineNumber)
{
// We're entering an inlined block.
// Accumulate its enclosing line numbers, so they can be
// printed out for each inlined block.
if (index > 0)
{
enclosingLineNumbers.push(previousInfo);
}
printInlinedMethodMapping(clazz.getName(),
methodName,
methodDescriptor,
info,
enclosingLineNumbers,
obfuscatedMethodName);
}
// TODO: There appear to be cases where the stack is empty at this point, so we've added a check.
else if (!enclosingLineNumbers.isEmpty())
{
// We're exiting an inlined block.
// Pop its enclosing line number.
enclosingLineNumbers.pop();
}
}
else if (source == null && previousSource != null)
{
// TODO: There appear to be cases where the stack is empty at this point, so we've added a check.
if (!enclosingLineNumbers.isEmpty())
{
// When exiting a top-level inlined block, the source might be null.
// See LineNumberLinearizer, line 185, exiting an inlined block.
enclosingLineNumbers.pop();
}
}
previousInfo = info;
}
printed = true;
}
// Small utility methods.
/**
* Prints out the mapping of the specified inlined methods and its
* enclosing methods.
*/
private void printInlinedMethodMapping(String className,
String methodName,
String methodDescriptor,
LineNumberInfo inlinedInfo,
Stack enclosingLineNumbers,
String obfuscatedMethodName)
{
String source = inlinedInfo.getSource();
// Parse the information from the source string of the
// inlined method.
int separatorIndex1 = source.indexOf('.');
int separatorIndex2 = source.indexOf('(', separatorIndex1 + 1);
int separatorIndex3 = source.indexOf(':', separatorIndex2 + 1);
int separatorIndex4 = source.indexOf(':', separatorIndex3 + 1);
String inlinedClassName = source.substring(0, separatorIndex1);
String inlinedMethodName = source.substring(separatorIndex1 + 1, separatorIndex2);
String inlinedMethodDescriptor = source.substring(separatorIndex2, separatorIndex3);
String inlinedRange = source.substring(separatorIndex3);
int startLineNumber = Integer.parseInt(source.substring(separatorIndex3 + 1, separatorIndex4));
int endLineNumber = Integer.parseInt(source.substring(separatorIndex4 + 1));
// Compute the shifted line number range.
int shiftedStartLineNumber = inlinedInfo.u2lineNumber;
int shiftedEndLineNumber = shiftedStartLineNumber + endLineNumber - startLineNumber;
// Print out the line number range of the inlined method.
pw.println(" " +
shiftedStartLineNumber + ":" +
shiftedEndLineNumber + ":" +
ClassUtil.externalMethodReturnType(inlinedMethodDescriptor) + " " +
(inlinedClassName.equals(className) ? "" :
ClassUtil.externalClassName(inlinedClassName) + JavaConstants.PACKAGE_SEPARATOR) +
inlinedMethodName + JavaConstants.METHOD_ARGUMENTS_OPEN +
ClassUtil.externalMethodArguments(inlinedMethodDescriptor) + JavaConstants.METHOD_ARGUMENTS_CLOSE +
inlinedRange + " -> " +
obfuscatedMethodName);
// Print out the line numbers of the accumulated enclosing
// methods.
for (int enclosingIndex = enclosingLineNumbers.size()-1; enclosingIndex >= 0; enclosingIndex--)
{
LineNumberInfo enclosingInfo =
(LineNumberInfo)enclosingLineNumbers.get(enclosingIndex);
printEnclosingMethodMapping(className,
methodName,
methodDescriptor,
shiftedStartLineNumber + ":" +
shiftedEndLineNumber,
enclosingInfo,
obfuscatedMethodName);
}
}
/**
* Prints out the mapping of the specified enclosing method.
*/
private void printEnclosingMethodMapping(String className,
String methodName,
String methodDescriptor,
String shiftedRange,
LineNumberInfo enclosingInfo,
String obfuscatedMethodName)
{
// Parse the information from the source string of the enclosing
// method.
String enclosingSource = enclosingInfo.getSource();
String enclosingClassName;
String enclosingMethodName;
String enclosingMethodDescriptor;
int enclosingLineNumber;
if (enclosingSource == null)
{
enclosingClassName = className;
enclosingMethodName = methodName;
enclosingMethodDescriptor = methodDescriptor;
enclosingLineNumber = enclosingInfo.u2lineNumber;
}
else
{
int enclosingSeparatorIndex1 = enclosingSource.indexOf('.');
int enclosingSeparatorIndex2 = enclosingSource.indexOf('(', enclosingSeparatorIndex1 + 1);
int enclosingSeparatorIndex3 = enclosingSource.indexOf(':', enclosingSeparatorIndex2 + 1);
int enclosingSeparatorIndex4 = enclosingSource.indexOf(':', enclosingSeparatorIndex3 + 1);
// We need the first line number to correct the shifted enclosing
// line number back to its original range.
int firstLineNumber = Integer.parseInt(enclosingSource.substring(enclosingSeparatorIndex3 + 1, enclosingSeparatorIndex4));
enclosingClassName = enclosingSource.substring(0, enclosingSeparatorIndex1);
enclosingMethodName = enclosingSource.substring(enclosingSeparatorIndex1 + 1, enclosingSeparatorIndex2);
enclosingMethodDescriptor = enclosingSource.substring(enclosingSeparatorIndex2, enclosingSeparatorIndex3);
enclosingLineNumber = (enclosingInfo.u2lineNumber - firstLineNumber) % LineNumberLinearizer.SHIFT_ROUNDING + firstLineNumber;
}
// Print out the line number of the enclosing method.
pw.println(" " +
shiftedRange + ":" +
ClassUtil.externalMethodReturnType(enclosingMethodDescriptor) + " " +
(enclosingClassName.equals(className) ? "" :
ClassUtil.externalClassName(enclosingClassName) + JavaConstants.PACKAGE_SEPARATOR) +
enclosingMethodName + JavaConstants.METHOD_ARGUMENTS_OPEN +
ClassUtil.externalMethodArguments(enclosingMethodDescriptor) + JavaConstants.METHOD_ARGUMENTS_CLOSE + ":" +
enclosingLineNumber + " -> " +
obfuscatedMethodName);
}
}