/* Software Name : AsmDex
* Version : 1.0
*
* Copyright © 2012 France Télécom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.ow2.asmdex.util;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import org.ow2.asmdex.ApplicationReader;
import org.ow2.asmdex.ApplicationVisitor;
import org.ow2.asmdex.ClassVisitor;
import org.ow2.asmdex.Opcodes;
/**
* An {@link ApplicationVisitor} that prints the AsmDex code that generates the Application
* it visits. This Application visitor can be used to quickly write ASM code to generate
* some given bytecode: <ul> <li>write the Java source code equivalent to the
* bytecode you want to generate;</li> <li>compile it with <tt>javac</tt>;</li>
* <li>make a {@link AsmDexifierApplicationVisitor} visit this compiled class (see the
* {@link #main main} method);</li> <li>edit the generated source code, if
* necessary.</li> </ul>
*
* NOTE:
* <ul>
* <li> Having all the dumps in one dump() method provides a code that is quickly superior to the 64k
* limit per method. So each class has its own dumpClassName() method, which is called by
* the dump() method.</li>
* <li> The Opcodes values are obtained through reflection. If refactoring, check out if
* OPCODES_FULLY_QUALIFIED_NAME is still valid.</li>
* </ul>
*
* @author Julien Névo, based on the ASM Framework.
*/
public class AsmDexifierApplicationVisitor extends ApplicationVisitor {
final private AsmDexPrinter pr;
/**
* Fully qualified name of the Opcodes class.
*/
public static final String OPCODES_FULLY_QUALIFIED_NAME = "org.ow2.asmdex.Opcodes";
/**
* How every Opcode names start.
*/
private static final String OPCODES_START = "INSN_";
/**
* Possible package name.
*/
private String packageName = null;
/**
* Possible path and filename of the file where to save the output.
*/
private String filenameOutput = null;
/**
* How many Opcodes there could be.
*/
private static final int NB_OPCODES = 256;
/**
* The print writer to be used to print the Application.
*/
protected final PrintWriter pw;
/**
* The Dex filename, without the path and extension.
*/
private static String dexFileName;
/**
* Classes to visit, or Null if all must be visited.
*/
private static String[] classesToVisit;
/**
* Array where all the opcode names are written.
*/
private static String[] opcodeNames;
/**
* List containing the name of the dumpMethods.
*/
private ArrayList<String> dumpMethods;
/**
* Used to store the text of the methods to call. We have to store it because we only
* know all the method names when everything has been visited.
*/
private TextComposite methodToCall;
/**
* Constructs a new {@link AsmDexifierApplicationVisitor} object.
*
* @param api API level
* @param pw the print writer to be used to print the class.
*/
public AsmDexifierApplicationVisitor(int api, final PrintWriter pw) {
super(api);
this.pw = pw;
pr = new AsmDexPrinter();
dumpMethods = new ArrayList<String>();
methodToCall = new TextComposite();
}
/**
* Constructs a new {@link AsmDexifierApplicationVisitor} object.
*
* @param api API level
* @param pw the print writer to be used to print the class.
* @param packageName name of the package, or Null if none should be written.
* @param filenameOutput possible path and filename of the file where to save the output.
* If not Null, a Main method will be created.
*/
public AsmDexifierApplicationVisitor(int api, final PrintWriter pw, final String packageName,
final String filenameOutput) {
this(api, pw);
this.packageName = packageName;
this.filenameOutput = filenameOutput;
}
/**
* Prints the ASM source code to generate the given Application to the standard
* output. Not to give classes means visiting all the classes of the Dex file.
* <p> Usage: ASMifierClassVisitor [-debug] <Dex file to open>
* [<fully qualified class names>]
*
* @param args the command line arguments.
*
* @throws Exception if the class cannot be found, or if an IO exception
* occurs.
*/
public static void main(final String[] args) throws Exception {
int i = 0;
int flags = ApplicationReader.SKIP_DEBUG;
int nbArgs = args.length;
boolean ok = (nbArgs > 0);
if (ok && "-debug".equals(args[0])) {
i = 1;
flags = 0;
if (nbArgs < 2) {
ok = false;
}
}
if ((!ok) || (!args[i].endsWith(".dex"))) {
System.err.println("Prints the AsmDex code to generate the given class.");
System.err.println("Usage: ASMifierApplicationVisitor [-debug] "
+ "<Dex file to open> "
+ "[<fully qualified class names>]");
return;
}
// Visits all the Classes or one/several ?
classesToVisit = null;
if (nbArgs > (i + 1)) {
int nbClasses = nbArgs - i - 1;
classesToVisit = new String[nbClasses];
int classIndex = 0;
for (int j = i + 1; j < nbArgs; j++) {
classesToVisit[classIndex++] = args[j];
}
}
dexFileName = trimDexFileName(args[i]);
// Sets up the Opcodes array by using Reflection.
opcodeNames = new String[NB_OPCODES];
Field[] fields = Class.forName(OPCODES_FULLY_QUALIFIED_NAME).getFields();
for (Field field : fields) {
String fieldName = field.getName();
int goodFlag = Modifier.FINAL | Modifier.STATIC | Modifier.PUBLIC;
if (fieldName.startsWith(OPCODES_START)) {
if (field.getModifiers() == goodFlag) {
int opcode = field.getInt(field);
if (opcode < NB_OPCODES) {
opcodeNames[opcode] = fieldName;
}
}
}
}
ApplicationReader ar = new ApplicationReader(Opcodes.ASM4, args[i]);
ar.accept(new AsmDexifierApplicationVisitor(Opcodes.ASM4, new PrintWriter(System.out)),
classesToVisit, flags);
}
/**
* Trims the given Dex filename of its path and extension.
* @param fullFileName name that can contain the path and the extension.
* @return name of the dex file, without the path and the extension.
*/
public static String trimDexFileName(String fullFileName) {
String trimmedFileName = fullFileName;
int n = trimmedFileName.lastIndexOf(".dex");
if (n != -1) {
trimmedFileName = trimmedFileName.substring(0, n);
}
n = trimmedFileName.lastIndexOf('/');
if (n != -1) {
trimmedFileName = trimmedFileName.substring(n + 1);
}
return trimmedFileName;
}
/**
* Sets the dex filename, which mustn't contain path and extension. It is useful to name
* the class generated (prefixed with "Dump").
* @param name the dex filename.
*/
public static void setDexFileName(String name) {
dexFileName = name;
}
// ------------------------------------------------
// Visitor Methods.
// ------------------------------------------------
@Override
public void visit() {
// Puts the first letter of the filename in upper case.
char firstLetter = Character.toUpperCase(dexFileName.charAt(0));
String otherLetters = dexFileName.length() > 1 ? dexFileName.substring(1) : "";
String camelName = firstLetter + otherLetters;
if (packageName != null) {
pr.addText("package " + packageName + ";\n");
pr.addEOL();
}
pr.addText("import java.util.*;\n");
if (filenameOutput != null) {
pr.addText("import java.io.*;\n");
}
pr.addText("import org.ow2.asmdex.*;\n");
pr.addText("import org.ow2.asmdex.structureCommon.*;\n\n");
pr.addText("public class " + camelName + "Dump implements Opcodes {\n\n");
if (filenameOutput != null) {
pr.addText("public static void main(String[] args) throws Exception {\n");
pr.addText(" byte[] b = dump();\n");
pr.addText(" File outputFile;\n");
pr.addText(" if (args.length == 1) {\n");
pr.addText(" outputFile = new File(args[0]);\n");
pr.addText(" FileOutputStream outputStream = new FileOutputStream(outputFile);\n");
pr.addText(" outputStream.write(b);\n");
pr.addText(" outputStream.close();\n");
pr.addText(" }\n");
pr.addText("}\n\n");
}
pr.addText("public static byte[] dump() throws Exception {\n\n");
pr.addText("\tApplicationWriter aw = new ApplicationWriter();\n");
pr.addText("\taw.visit();\n");
pr.addEOL();
pr.closeText();
// Prepare the calls to the various dump Methods.
pr.addTextToList(methodToCall);
pr.addEOL();
pr.addText("\taw.visitEnd();\n\n");
pr.addText("\treturn aw.toByteArray();\n");
pr.addText("}\n\n");
pr.closeText();
}
@Override
public ClassVisitor visitClass(int access, String name, String[] signature,
String superName, String[] interfaces) {
String className = getAcceptableMethodName(name);
pr.addText("public static void ");
pr.addText(className);
pr.addText("(ApplicationWriter aw) {\n");
pr.addText("\tClassVisitor cv;\n");
pr.addText("\tFieldVisitor fv;\n");
pr.addText("\tMethodVisitor mv;\n");
pr.addText("\tAnnotationVisitor av0;\n\n");
pr.addText("\tcv = aw.visitClass(");
pr.addAccessFlags(access, true);
pr.addConstant(name, true);
pr.addConstant(signature, true);
pr.addConstant(superName, true);
pr.addConstant(interfaces, false);
pr.addText(");");
pr.addEOL();
pr.closeText();
AsmDexifierClassVisitor cv = new AsmDexifierClassVisitor(api, 1);
pr.addTextToList(cv.getTextComponent());
pr.addText("}\n\n");
pr.closeText();
return cv;
}
@Override
public void visitEnd() {
pr.addText("}\n\n");
pr.closeText();
for (String methodName : dumpMethods) {
StringBuffer sb = new StringBuffer("\t" + methodName);
sb.append("(aw);\n");
TextLeaf leaf = new TextLeaf(sb);
methodToCall.addComponent(leaf);
}
// Prints the String Tree.
pr.getTextComponent().print(pw);
pw.flush();
}
// ------------------------------------------------
// Static utility Methods.
// ------------------------------------------------
/**
* Returns the opcode name from the given opcode, or Null if the opcode isn't found.
* @param opcode the opcode.
* @return the opcode name from the given opcode, or Null if the opcode isn't found.
*/
public static String getOpcodeName(int opcode) {
String result = null;
if ((opcodeNames != null) && (opcode < opcodeNames.length)) {
result = opcodeNames[opcode];
}
return result;
}
/**
* Returns a correct Method name from the given Class name, which may probably be
* fully qualified. If also must not already be in the dumpMethods list, in which
* case a number is added to the name.
* @param className
* @return a correct Class name.
*/
public String getAcceptableMethodName(String className) {
String result = "Method"; // Fail-safe name.
if ((className != null) && (!className.equals(""))) {
int i = className.lastIndexOf('/');
if (i >= 0) {
result = className.substring(i + 1);
} else {
result = className;
}
i = result.lastIndexOf(';');
if (i > 0) {
result = result.substring(0, i);
}
}
result = "dump" + result;
// Checks if this name isn't already in the list.
if (dumpMethods.contains(result)) {
boolean stillFound = true;
int i = 2;
String newResult = null;
while (stillFound) {
newResult = result + i;
stillFound = dumpMethods.contains(newResult);
i++;
}
result = newResult;
}
dumpMethods.add(result);
return result;
}
}