/* * 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.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.PrintStream; 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 PrintStream ps; // A field serving as a return value for the visitor methods. private boolean printed; /** * Creates a new MappingPrinter that prints to <code>System.out</code>. */ public MappingPrinter() { this(System.out); } /** * Creates a new MappingPrinter that prints to the given stream. * @param printStream the stream to which to print */ public MappingPrinter(PrintStream printStream) { this.ps = printStream; } // Implementations for ClassVisitor. public void visitProgramClass(ProgramClass programClass) { String name = programClass.getName(); String newName = ClassObfuscator.newClassName(programClass); // Print out the class mapping. ps.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. ps.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) { ps.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. ps.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. ps.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(); if (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(); } } 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. ps.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. ps.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); } }