BCELifier.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.bcel.util;

  18. import java.io.IOException;
  19. import java.io.OutputStream;
  20. import java.io.OutputStreamWriter;
  21. import java.io.PrintWriter;
  22. import java.nio.charset.StandardCharsets;
  23. import java.util.Locale;

  24. import org.apache.bcel.Const;
  25. import org.apache.bcel.Repository;
  26. import org.apache.bcel.classfile.ClassParser;
  27. import org.apache.bcel.classfile.Code;
  28. import org.apache.bcel.classfile.ConstantValue;
  29. import org.apache.bcel.classfile.ExceptionTable;
  30. import org.apache.bcel.classfile.Field;
  31. import org.apache.bcel.classfile.JavaClass;
  32. import org.apache.bcel.classfile.Method;
  33. import org.apache.bcel.classfile.StackMap;
  34. import org.apache.bcel.classfile.StackMapEntry;
  35. import org.apache.bcel.classfile.StackMapType;
  36. import org.apache.bcel.classfile.Utility;
  37. import org.apache.bcel.generic.ArrayType;
  38. import org.apache.bcel.generic.ConstantPoolGen;
  39. import org.apache.bcel.generic.MethodGen;
  40. import org.apache.bcel.generic.Type;
  41. import org.apache.commons.lang3.ArrayUtils;
  42. import org.apache.commons.lang3.StringUtils;

  43. /**
  44.  * This class takes a given JavaClass object and converts it to a Java program that creates that very class using BCEL.
  45.  * This gives new users of BCEL a useful example showing how things are done with BCEL. It does not cover all features
  46.  * of BCEL, but tries to mimic hand-written code as close as possible.
  47.  */
  48. public class BCELifier extends org.apache.bcel.classfile.EmptyVisitor {

  49.     /**
  50.      * Enum corresponding to flag source.
  51.      */
  52.     public enum FLAGS {
  53.         UNKNOWN, CLASS, METHOD,
  54.     }

  55.     // The base package name for imports; assumes Const is at the top level
  56.     // N.B we use the class so renames will be detected by the compiler/IDE
  57.     private static final String BASE_PACKAGE = Const.class.getPackage().getName();
  58.     private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + ".";

  59.     // Needs to be accessible from unit test code
  60.     static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException {
  61.         JavaClass javaClass;
  62.         if ((javaClass = Repository.lookupClass(name)) == null) {
  63.             javaClass = new ClassParser(name).parse(); // May throw IOException
  64.         }
  65.         return javaClass;
  66.     }

  67.     /**
  68.      * Default main method
  69.      */
  70.     public static void main(final String[] argv) throws Exception {
  71.         if (argv.length != 1) {
  72.             System.out.println("Usage: BCELifier className");
  73.             System.out.println("\tThe class must exist on the classpath");
  74.             return;
  75.         }
  76.         final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out);
  77.         bcelifier.start();
  78.     }

  79.     static String printArgumentTypes(final Type[] argTypes) {
  80.         if (argTypes.length == 0) {
  81.             return "Type.NO_ARGS";
  82.         }
  83.         final StringBuilder args = new StringBuilder();
  84.         for (int i = 0; i < argTypes.length; i++) {
  85.             args.append(printType(argTypes[i]));
  86.             if (i < argTypes.length - 1) {
  87.                 args.append(", ");
  88.             }
  89.         }
  90.         return "new Type[] { " + args.toString() + " }";
  91.     }

  92.     static String printFlags(final int flags) {
  93.         return printFlags(flags, FLAGS.UNKNOWN);
  94.     }

  95.     /**
  96.      * Return a string with the flag settings
  97.      *
  98.      * @param flags the flags field to interpret
  99.      * @param location the item type
  100.      * @return the formatted string
  101.      * @since 6.0 made public
  102.      */
  103.     public static String printFlags(final int flags, final FLAGS location) {
  104.         if (flags == 0) {
  105.             return "0";
  106.         }
  107.         final StringBuilder buf = new StringBuilder();
  108.         for (int i = 0, pow = 1; pow <= Const.MAX_ACC_FLAG_I; i++) {
  109.             if ((flags & pow) != 0) {
  110.                 if (pow == Const.ACC_SYNCHRONIZED && location == FLAGS.CLASS) {
  111.                     buf.append(CONSTANT_PREFIX).append("ACC_SUPER | ");
  112.                 } else if (pow == Const.ACC_VOLATILE && location == FLAGS.METHOD) {
  113.                     buf.append(CONSTANT_PREFIX).append("ACC_BRIDGE | ");
  114.                 } else if (pow == Const.ACC_TRANSIENT && location == FLAGS.METHOD) {
  115.                     buf.append(CONSTANT_PREFIX).append("ACC_VARARGS | ");
  116.                 } else if (i < Const.ACCESS_NAMES_LENGTH) {
  117.                     buf.append(CONSTANT_PREFIX).append("ACC_").append(Const.getAccessName(i).toUpperCase(Locale.ENGLISH)).append(" | ");
  118.                 } else {
  119.                     buf.append(String.format(CONSTANT_PREFIX + "ACC_BIT %x | ", pow));
  120.                 }
  121.             }
  122.             pow <<= 1;
  123.         }
  124.         final String str = buf.toString();
  125.         return str.substring(0, str.length() - 3);
  126.     }

  127.     static String printType(final String signature) {
  128.         final Type type = Type.getType(signature);
  129.         final byte t = type.getType();
  130.         if (t <= Const.T_VOID) {
  131.             return "Type." + Const.getTypeName(t).toUpperCase(Locale.ENGLISH);
  132.         }
  133.         if (type.toString().equals("java.lang.String")) {
  134.             return "Type.STRING";
  135.         }
  136.         if (type.toString().equals("java.lang.Object")) {
  137.             return "Type.OBJECT";
  138.         }
  139.         if (type.toString().equals("java.lang.StringBuffer")) {
  140.             return "Type.STRINGBUFFER";
  141.         }
  142.         if (type instanceof ArrayType) {
  143.             final ArrayType at = (ArrayType) type;
  144.             return "new ArrayType(" + printType(at.getBasicType()) + ", " + at.getDimensions() + ")";
  145.         }
  146.         return "new ObjectType(\"" + Utility.signatureToString(signature, false) + "\")";
  147.     }

  148.     static String printType(final Type type) {
  149.         return printType(type.getSignature());
  150.     }

  151.     private final JavaClass clazz;

  152.     private final PrintWriter printWriter;

  153.     private final ConstantPoolGen constantPoolGen;

  154.     /**
  155.      * Constructs a new instance.
  156.      *
  157.      * @param clazz Java class to "decompile".
  158.      * @param out where to print the Java program in UTF-8.
  159.      */
  160.     public BCELifier(final JavaClass clazz, final OutputStream out) {
  161.         this.clazz = clazz;
  162.         this.printWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), false);
  163.         this.constantPoolGen = new ConstantPoolGen(this.clazz.getConstantPool());
  164.     }

  165.     private void printCreate() {
  166.         printWriter.println("  public void create(OutputStream out) throws IOException {");
  167.         final Field[] fields = clazz.getFields();
  168.         if (fields.length > 0) {
  169.             printWriter.println("    createFields();");
  170.         }
  171.         final Method[] methods = clazz.getMethods();
  172.         for (int i = 0; i < methods.length; i++) {
  173.             printWriter.println("    createMethod_" + i + "();");
  174.         }
  175.         printWriter.println("    _cg.getJavaClass().dump(out);");
  176.         printWriter.println("  }");
  177.         printWriter.println();
  178.     }

  179.     private void printMain() {
  180.         final String className = clazz.getClassName();
  181.         printWriter.println("  public static void main(String[] args) throws Exception {");
  182.         printWriter.println("    " + className + "Creator creator = new " + className + "Creator();");
  183.         printWriter.println("    creator.create(new FileOutputStream(\"" + className + ".class\"));");
  184.         printWriter.println("  }");
  185.     }

  186.     /**
  187.      * Start Java code generation
  188.      */
  189.     public void start() {
  190.         visitJavaClass(clazz);
  191.         printWriter.flush();
  192.     }

  193.     @Override
  194.     public void visitField(final Field field) {
  195.         printWriter.println();
  196.         printWriter.println(
  197.             "    field = new FieldGen(" + printFlags(field.getAccessFlags()) + ", " + printType(field.getSignature()) + ", \"" + field.getName() + "\", _cp);");
  198.         final ConstantValue cv = field.getConstantValue();
  199.         if (cv != null) {
  200.             printWriter.print("    field.setInitValue(");
  201.             if (field.getType() == Type.CHAR) {
  202.                 printWriter.print("(char)");
  203.             }
  204.             if (field.getType() == Type.SHORT) {
  205.                 printWriter.print("(short)");
  206.             }
  207.             if (field.getType() == Type.BYTE) {
  208.                 printWriter.print("(byte)");
  209.             }
  210.             printWriter.print(cv);
  211.             if (field.getType() == Type.LONG) {
  212.                 printWriter.print("L");
  213.             }
  214.             if (field.getType() == Type.FLOAT) {
  215.                 printWriter.print("F");
  216.             }
  217.             if (field.getType() == Type.DOUBLE) {
  218.                 printWriter.print("D");
  219.             }
  220.             printWriter.println(");");
  221.         }
  222.         printWriter.println("    _cg.addField(field.getField());");
  223.     }

  224.     @Override
  225.     public void visitJavaClass(final JavaClass clazz) {
  226.         String className = clazz.getClassName();
  227.         final String superName = clazz.getSuperclassName();
  228.         final String packageName = clazz.getPackageName();
  229.         final String inter = Utility.printArray(clazz.getInterfaceNames(), false, true);
  230.         if (StringUtils.isNotEmpty(packageName)) {
  231.             className = className.substring(packageName.length() + 1);
  232.             printWriter.println("package " + packageName + ";");
  233.             printWriter.println();
  234.         }
  235.         printWriter.println("import " + BASE_PACKAGE + ".generic.*;");
  236.         printWriter.println("import " + BASE_PACKAGE + ".classfile.*;");
  237.         printWriter.println("import " + BASE_PACKAGE + ".*;");
  238.         printWriter.println("import java.io.*;");
  239.         printWriter.println();
  240.         printWriter.println("public class " + className + "Creator {");
  241.         printWriter.println("  private InstructionFactory _factory;");
  242.         printWriter.println("  private ConstantPoolGen    _cp;");
  243.         printWriter.println("  private ClassGen           _cg;");
  244.         printWriter.println();
  245.         printWriter.println("  public " + className + "Creator() {");
  246.         printWriter.println("    _cg = new ClassGen(\"" + (packageName.isEmpty() ? className : packageName + "." + className) + "\", \"" + superName
  247.             + "\", " + "\"" + clazz.getSourceFileName() + "\", " + printFlags(clazz.getAccessFlags(), FLAGS.CLASS) + ", " + "new String[] { " + inter + " });");
  248.         printWriter.println("    _cg.setMajor(" + clazz.getMajor() + ");");
  249.         printWriter.println("    _cg.setMinor(" + clazz.getMinor() + ");");
  250.         printWriter.println();
  251.         printWriter.println("    _cp = _cg.getConstantPool();");
  252.         printWriter.println("    _factory = new InstructionFactory(_cg, _cp);");
  253.         printWriter.println("  }");
  254.         printWriter.println();
  255.         printCreate();
  256.         final Field[] fields = clazz.getFields();
  257.         if (fields.length > 0) {
  258.             printWriter.println("  private void createFields() {");
  259.             printWriter.println("    FieldGen field;");
  260.             for (final Field field : fields) {
  261.                 field.accept(this);
  262.             }
  263.             printWriter.println("  }");
  264.             printWriter.println();
  265.         }
  266.         final Method[] methods = clazz.getMethods();
  267.         for (int i = 0; i < methods.length; i++) {
  268.             printWriter.println("  private void createMethod_" + i + "() {");
  269.             methods[i].accept(this);
  270.             printWriter.println("  }");
  271.             printWriter.println();
  272.         }
  273.         printMain();
  274.         printWriter.println("}");
  275.     }

  276.     @Override
  277.     public void visitMethod(final Method method) {
  278.         final MethodGen mg = new MethodGen(method, clazz.getClassName(), constantPoolGen);
  279.         printWriter.println("    InstructionList il = new InstructionList();");
  280.         printWriter.println("    MethodGen method = new MethodGen(" + printFlags(method.getAccessFlags(), FLAGS.METHOD) + ", " + printType(mg.getReturnType())
  281.             + ", " + printArgumentTypes(mg.getArgumentTypes()) + ", " + "new String[] { " + Utility.printArray(mg.getArgumentNames(), false, true) + " }, \""
  282.             + method.getName() + "\", \"" + clazz.getClassName() + "\", il, _cp);");
  283.         final ExceptionTable exceptionTable = method.getExceptionTable();
  284.         if (exceptionTable != null) {
  285.             final String[] exceptionNames = exceptionTable.getExceptionNames();
  286.             for (final String exceptionName : exceptionNames) {
  287.                 printWriter.print("    method.addException(\"");
  288.                 printWriter.print(exceptionName);
  289.                 printWriter.println("\");");
  290.             }
  291.         }
  292.         final Code code = method.getCode();
  293.         if (code != null) {
  294.             final StackMap stackMap = code.getStackMap();
  295.             if (stackMap != null) {
  296.                 stackMap.accept(this);
  297.             }
  298.         }
  299.         printWriter.println();
  300.         final BCELFactory factory = new BCELFactory(mg, printWriter);
  301.         factory.start();
  302.         printWriter.println("    method.setMaxStack();");
  303.         printWriter.println("    method.setMaxLocals();");
  304.         printWriter.println("    _cg.addMethod(method.getMethod());");
  305.         printWriter.println("    il.dispose();");
  306.     }

  307.     @Override
  308.     public void visitStackMap(final StackMap stackMap) {
  309.         super.visitStackMap(stackMap);
  310.         printWriter.print("    method.addCodeAttribute(");
  311.         printWriter.print("new StackMap(_cp.addUtf8(\"");
  312.         printWriter.print(stackMap.getName());
  313.         printWriter.print("\"), ");
  314.         printWriter.print(stackMap.getLength());
  315.         printWriter.print(", ");
  316.         printWriter.print("new StackMapEntry[] {");
  317.         final StackMapEntry[] table = stackMap.getStackMap();
  318.         for (int i = 0; i < table.length; i++) {
  319.             table[i].accept(this);
  320.             if (i < table.length - 1) {
  321.                 printWriter.print(", ");
  322.             } else {
  323.                 printWriter.print(" }");
  324.             }
  325.         }
  326.         printWriter.print(", _cp.getConstantPool())");
  327.         printWriter.println(");");
  328.     }

  329.     @Override
  330.     public void visitStackMapEntry(final StackMapEntry stackMapEntry) {
  331.         super.visitStackMapEntry(stackMapEntry);
  332.         printWriter.print("new StackMapEntry(");
  333.         printWriter.print(stackMapEntry.getFrameType());
  334.         printWriter.print(", ");
  335.         printWriter.print(stackMapEntry.getByteCodeOffset());
  336.         printWriter.print(", ");
  337.         visitStackMapTypeArray(stackMapEntry.getTypesOfLocals());
  338.         printWriter.print(", ");
  339.         visitStackMapTypeArray(stackMapEntry.getTypesOfStackItems());
  340.         printWriter.print(", _cp.getConstantPool())");
  341.     }

  342.     /**
  343.      * Visits a {@link StackMapType} object.
  344.      * @param stackMapType object to visit
  345.      * @since 6.7.1
  346.      */
  347.     @Override
  348.     public void visitStackMapType(final StackMapType stackMapType) {
  349.         super.visitStackMapType(stackMapType);
  350.         printWriter.print("new StackMapType((byte)");
  351.         printWriter.print(stackMapType.getType());
  352.         printWriter.print(", ");
  353.         if (stackMapType.hasIndex()) {
  354.             printWriter.print("_cp.addClass(\"");
  355.             printWriter.print(stackMapType.getClassName());
  356.             printWriter.print("\")");
  357.         } else {
  358.             printWriter.print("-1");
  359.         }
  360.         printWriter.print(", _cp.getConstantPool())");
  361.     }

  362.     private void visitStackMapTypeArray(final StackMapType[] types) {
  363.         if (ArrayUtils.isEmpty(types)) {
  364.             printWriter.print("null"); // null translates to StackMapType.EMPTY_ARRAY
  365.         } else {
  366.             printWriter.print("new StackMapType[] {");
  367.             for (int i = 0; i < types.length; i++) {
  368.                 types[i].accept(this);
  369.                 if (i < types.length - 1) {
  370.                     printWriter.print(", ");
  371.                 } else {
  372.                     printWriter.print(" }");
  373.                 }
  374.             }
  375.         }
  376.     }
  377. }