    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.bcel.util;
    020
    021import java.io.IOException;
    022import java.io.OutputStream;
    023import java.io.OutputStreamWriter;
    024import java.io.PrintWriter;
    025import java.nio.charset.StandardCharsets;
    026
    027import org.apache.bcel.Const;
    028import org.apache.bcel.Repository;
    029import org.apache.bcel.classfile.ClassParser;
    030import org.apache.bcel.classfile.Code;
    031import org.apache.bcel.classfile.ConstantValue;
    032import org.apache.bcel.classfile.ExceptionTable;
    033import org.apache.bcel.classfile.Field;
    034import org.apache.bcel.classfile.JavaClass;
    035import org.apache.bcel.classfile.Method;
    036import org.apache.bcel.classfile.StackMap;
    037import org.apache.bcel.classfile.StackMapEntry;
    038import org.apache.bcel.classfile.StackMapType;
    039import org.apache.bcel.classfile.Utility;
    040import org.apache.bcel.generic.ArrayType;
    041import org.apache.bcel.generic.ConstantPoolGen;
    042import org.apache.bcel.generic.MethodGen;
    043import org.apache.bcel.generic.Type;
    044import org.apache.commons.lang3.ArrayUtils;
    045import org.apache.commons.lang3.StringUtils;
    046
    047/**
    048 * This class takes a given JavaClass object and converts it to a Java program that creates that very class using BCEL.
    049 * This gives new users of BCEL a useful example showing how things are done with BCEL. It does not cover all features
    050 * of BCEL, but tries to mimic hand-written code as close as possible.
    051 */
    052public class BCELifier extends org.apache.bcel.classfile.EmptyVisitor {
    053
    054    /**
    055     * Enum corresponding to flag source.
    056     */
    057    public enum FLAGS {
    058
    059        /** Unknown flag source. */
    060        UNKNOWN,
    061
    062        /** Class flag source. */
    063        CLASS,
    064
    065        /** Method flag source. */
    066        METHOD,
    067    }
    068
    069    // The base package name for imports; assumes Const is at the top level
    070    // N.B we use the class so renames will be detected by the compiler/IDE
    071    private static final String BASE_PACKAGE = Const.class.getPackage().getName();
    072    private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + ".";
    073
    074    // Needs to be accessible from unit test code
    075    static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException {
    076        JavaClass javaClass;
    077        if ((javaClass = Repository.lookupClass(name)) == null) {
    078            javaClass = new ClassParser(name).parse(); // May throw IOException
    079        }
    080        return javaClass;
    081    }
    082
    083    /**
    084     * Default main method.
    085     *
    086     * @param argv command line arguments.
    087     * @throws Exception if an error occurs.
    088     */
    089    public static void main(final String[] argv) throws Exception {
    090        if (argv.length != 1) {
    091            System.out.println("Usage: BCELifier className");
    092            System.out.println("\tThe class must exist on the classpath");
    093            return;
    094        }
    095        final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out);
    096        bcelifier.start();
    097    }
    098
    099    static String printArgumentTypes(final Type[] argTypes) {
    100        if (argTypes.length == 0) {
    101            return "Type.NO_ARGS";
    102        }
    103        final StringBuilder args = new StringBuilder();
    104        for (int i = 0; i < argTypes.length; i++) {
    105            args.append(printType(argTypes[i]));
    106            if (i < argTypes.length - 1) {
    107                args.append(", ");
    108            }
    109        }
    110        return "new Type[] { " + args.toString() + " }";
    111    }
    112
    113    static String printFlags(final int flags) {
    114        return printFlags(flags, FLAGS.UNKNOWN);
    115    }
    116
    117    /**
    118     * Return a string with the flag settings
    119     *
    120     * @param flags the flags field to interpret.
    121     * @param location the item type.
    122     * @return the formatted string.
    123     * @since 6.0 made public
    124     */
    125    public static String printFlags(final int flags, final FLAGS location) {
    126        if (flags == 0) {
    127            return "0";
    128        }
    129        final StringBuilder buf = new StringBuilder();
    130        for (int i = 0, pow = 1; pow <= Const.MAX_ACC_FLAG_I; i++) {
    131            if ((flags & pow) != 0) {
    132                if (pow == Const.ACC_SYNCHRONIZED && location == FLAGS.CLASS) {
    133                    buf.append(CONSTANT_PREFIX).append("ACC_SUPER | ");
    134                } else if (pow == Const.ACC_VOLATILE && location == FLAGS.METHOD) {
    135                    buf.append(CONSTANT_PREFIX).append("ACC_BRIDGE | ");
    136                } else if (pow == Const.ACC_TRANSIENT && location == FLAGS.METHOD) {
    137                    buf.append(CONSTANT_PREFIX).append("ACC_VARARGS | ");
    138                } else if (i < Const.ACCESS_NAMES_LENGTH) {
    139                    buf.append(CONSTANT_PREFIX).append("ACC_").append(StringUtils.toRootUpperCase(Const.getAccessName(i))).append(" | ");
    140                } else {
    141                    buf.append(String.format(CONSTANT_PREFIX + "ACC_BIT %x | ", pow));
    142                }
    143            }
    144            pow <<= 1;
    145        }
    146        final String str = buf.toString();
    147        return str.substring(0, str.length() - 3);
    148    }
    149
    150    static String printType(final String signature) {
    151        final Type type = Type.getType(signature);
    152        final byte t = type.getType();
    153        if (t <= Const.T_VOID) {
    154            return "Type." + StringUtils.toRootUpperCase(Const.getTypeName(t));
    155        }
    156        if (type.toString().equals("java.lang.String")) {
    157            return "Type.STRING";
    158        }
    159        if (type.toString().equals("java.lang.Object")) {
    160            return "Type.OBJECT";
    161        }
    162        if (type.toString().equals("java.lang.StringBuffer")) {
    163            return "Type.STRINGBUFFER";
    164        }
    165        if (type instanceof ArrayType) {
    166            final ArrayType at = (ArrayType) type;
    167            return "new ArrayType(" + printType(at.getBasicType()) + ", " + at.getDimensions() + ")";
    168        }
    169        return "new ObjectType(\"" + Utility.signatureToString(signature, false) + "\")";
    170    }
    171
    172    static String printType(final Type type) {
    173        return printType(type.getSignature());
    174    }
    175
    176    private final JavaClass clazz;
    177
    178    private final PrintWriter printWriter;
    179
    180    private final ConstantPoolGen constantPoolGen;
    181
    182    /**
    183     * Constructs a new instance.
    184     *
    185     * @param clazz Java class to "decompile".
    186     * @param out where to print the Java program in UTF-8.
    187     */
    188    public BCELifier(final JavaClass clazz, final OutputStream out) {
    189        this.clazz = clazz;
    190        this.printWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), false);
    191        this.constantPoolGen = new ConstantPoolGen(this.clazz.getConstantPool());
    192    }
    193
    194    private void printCreate() {
    195        printWriter.println("  public void create(OutputStream out) throws IOException {");
    196        final Field[] fields = clazz.getFields();
    197        if (fields.length > 0) {
    198            printWriter.println("    createFields();");
    199        }
    200        final Method[] methods = clazz.getMethods();
    201        for (int i = 0; i < methods.length; i++) {
    202            printWriter.println("    createMethod_" + i + "();");
    203        }
    204        printWriter.println("    _cg.getJavaClass().dump(out);");
    205        printWriter.println("  }");
    206        printWriter.println();
    207    }
    208
    209    private void printMain() {
    210        final String className = clazz.getClassName();
    211        printWriter.println("  public static void main(String[] args) throws Exception {");
    212        printWriter.println("    " + className + "Creator creator = new " + className + "Creator();");
    213        printWriter.println("    creator.create(new FileOutputStream(\"" + className + ".class\"));");
    214        printWriter.println("  }");
    215    }
    216
    217    /**
    218     * Start Java code generation
    219     */
    220    public void start() {
    221        visitJavaClass(clazz);
    222        printWriter.flush();
    223    }
    224
    225    @Override
    226    public void visitField(final Field field) {
    227        printWriter.println();
    228        printWriter.println(
    229            "    field = new FieldGen(" + printFlags(field.getAccessFlags()) + ", " + printType(field.getSignature()) + ", \"" + field.getName() + "\", _cp);");
    230        final ConstantValue cv = field.getConstantValue();
    231        if (cv != null) {
    232            printWriter.print("    field.setInitValue(");
    233            if (field.getType() == Type.CHAR) {
    234                printWriter.print("(char)");
    235            }
    236            if (field.getType() == Type.SHORT) {
    237                printWriter.print("(short)");
    238            }
    239            if (field.getType() == Type.BYTE) {
    240                printWriter.print("(byte)");
    241            }
    242            printWriter.print(cv);
    243            if (field.getType() == Type.LONG) {
    244                printWriter.print("L");
    245            }
    246            if (field.getType() == Type.FLOAT) {
    247                printWriter.print("F");
    248            }
    249            if (field.getType() == Type.DOUBLE) {
    250                printWriter.print("D");
    251            }
    252            printWriter.println(");");
    253        }
    254        printWriter.println("    _cg.addField(field.getField());");
    255    }
    256
    257    @Override
    258    public void visitJavaClass(final JavaClass clazz) {
    259        String className = clazz.getClassName();
    260        final String superName = clazz.getSuperclassName();
    261        final String packageName = clazz.getPackageName();
    262        final String inter = Utility.printArray(clazz.getInterfaceNames(), false, true);
    263        if (StringUtils.isNotEmpty(packageName)) {
    264            className = className.substring(packageName.length() + 1);
    265            printWriter.println("package " + packageName + ";");
    266            printWriter.println();
    267        }
    268        printWriter.println("import " + BASE_PACKAGE + ".generic.*;");
    269        printWriter.println("import " + BASE_PACKAGE + ".classfile.*;");
    270        printWriter.println("import " + BASE_PACKAGE + ".*;");
    271        printWriter.println("import java.io.*;");
    272        printWriter.println();
    273        printWriter.println("public class " + className + "Creator {");
    274        printWriter.println("  private InstructionFactory _factory;");
    275        printWriter.println("  private ConstantPoolGen    _cp;");
    276        printWriter.println("  private ClassGen           _cg;");
    277        printWriter.println();
    278        printWriter.println("  public " + className + "Creator() {");
    279        printWriter.println("    _cg = new ClassGen(\"" + (packageName.isEmpty() ? className : packageName + "." + className) + "\", \"" + superName
    280            + "\", \"" + clazz.getSourceFileName() + "\", " + printFlags(clazz.getAccessFlags(), FLAGS.CLASS) + ", " + "new String[] { " + inter + " });");
    281        printWriter.println("    _cg.setMajor(" + clazz.getMajor() + ");");
    282        printWriter.println("    _cg.setMinor(" + clazz.getMinor() + ");");
    283        printWriter.println();
    284        printWriter.println("    _cp = _cg.getConstantPool();");
    285        printWriter.println("    _factory = new InstructionFactory(_cg, _cp);");
    286        printWriter.println("  }");
    287        printWriter.println();
    288        printCreate();
    289        final Field[] fields = clazz.getFields();
    290        if (fields.length > 0) {
    291            printWriter.println("  private void createFields() {");
    292            printWriter.println("    FieldGen field;");
    293            for (final Field field : fields) {
    294                field.accept(this);
    295            }
    296            printWriter.println("  }");
    297            printWriter.println();
    298        }
    299        final Method[] methods = clazz.getMethods();
    300        for (int i = 0; i < methods.length; i++) {
    301            printWriter.println("  private void createMethod_" + i + "() {");
    302            methods[i].accept(this);
    303            printWriter.println("  }");
    304            printWriter.println();
    305        }
    306        printMain();
    307        printWriter.println("}");
    308    }
    309
    310    @Override
    311    public void visitMethod(final Method method) {
    312        final MethodGen mg = new MethodGen(method, clazz.getClassName(), constantPoolGen);
    313        printWriter.println("    InstructionList il = new InstructionList();");
    314        printWriter.println("    MethodGen method = new MethodGen(" + printFlags(method.getAccessFlags(), FLAGS.METHOD) + ", " + printType(mg.getReturnType())
    315            + ", " + printArgumentTypes(mg.getArgumentTypes()) + ", new String[] { " + Utility.printArray(mg.getArgumentNames(), false, true) + " }, \""
    316            + method.getName() + "\", \"" + clazz.getClassName() + "\", il, _cp);");
    317        final ExceptionTable exceptionTable = method.getExceptionTable();
    318        if (exceptionTable != null) {
    319            final String[] exceptionNames = exceptionTable.getExceptionNames();
    320            for (final String exceptionName : exceptionNames) {
    321                printWriter.print("    method.addException(\"");
    322                printWriter.print(exceptionName);
    323                printWriter.println("\");");
    324            }
    325        }
    326        final Code code = method.getCode();
    327        if (code != null) {
    328            final StackMap stackMap = code.getStackMap();
    329            if (stackMap != null) {
    330                stackMap.accept(this);
    331            }
    332        }
    333        printWriter.println();
    334        final BCELFactory factory = new BCELFactory(mg, printWriter);
    335        factory.start();
    336        printWriter.println("    method.setMaxStack();");
    337        printWriter.println("    method.setMaxLocals();");
    338        printWriter.println("    _cg.addMethod(method.getMethod());");
    339        printWriter.println("    il.dispose();");
    340    }
    341
    342    @Override
    343    public void visitStackMap(final StackMap stackMap) {
    344        super.visitStackMap(stackMap);
    345        printWriter.print("    method.addCodeAttribute(");
    346        printWriter.print("new StackMap(_cp.addUtf8(\"");
    347        printWriter.print(stackMap.getName());
    348        printWriter.print("\"), ");
    349        printWriter.print(stackMap.getLength());
    350        printWriter.print(", ");
    351        printWriter.print("new StackMapEntry[] {");
    352        final StackMapEntry[] table = stackMap.getStackMap();
    353        for (int i = 0; i < table.length; i++) {
    354            table[i].accept(this);
    355            if (i < table.length - 1) {
    356                printWriter.print(", ");
    357            } else {
    358                printWriter.print(" }");
    359            }
    360        }
    361        printWriter.print(", _cp.getConstantPool())");
    362        printWriter.println(");");
    363    }
    364
    365    @Override
    366    public void visitStackMapEntry(final StackMapEntry stackMapEntry) {
    367        super.visitStackMapEntry(stackMapEntry);
    368        printWriter.print("new StackMapEntry(");
    369        printWriter.print(stackMapEntry.getFrameType());
    370        printWriter.print(", ");
    371        printWriter.print(stackMapEntry.getByteCodeOffset());
    372        printWriter.print(", ");
    373        visitStackMapTypeArray(stackMapEntry.getTypesOfLocals());
    374        printWriter.print(", ");
    375        visitStackMapTypeArray(stackMapEntry.getTypesOfStackItems());
    376        printWriter.print(", _cp.getConstantPool())");
    377    }
    378
    379    /**
    380     * Visits a {@link StackMapType} object.
    381     *
    382     * @param stackMapType object to visit.
    383     * @since 6.7.1
    384     */
    385    @Override
    386    public void visitStackMapType(final StackMapType stackMapType) {
    387        super.visitStackMapType(stackMapType);
    388        printWriter.print("new StackMapType((byte)");
    389        printWriter.print(stackMapType.getType());
    390        printWriter.print(", ");
    391        if (stackMapType.hasIndex()) {
    392            printWriter.print("_cp.addClass(\"");
    393            printWriter.print(stackMapType.getClassName());
    394            printWriter.print("\")");
    395        } else {
    396            printWriter.print("-1");
    397        }
    398        printWriter.print(", _cp.getConstantPool())");
    399    }
    400
    401    private void visitStackMapTypeArray(final StackMapType[] types) {
    402        if (ArrayUtils.isEmpty(types)) {
    403            printWriter.print("null"); // null translates to StackMapType.EMPTY_ARRAY
    404        } else {
    405            printWriter.print("new StackMapType[] {");
    406            for (int i = 0; i < types.length; i++) {
    407                types[i].accept(this);
    408                if (i < types.length - 1) {
    409                    printWriter.print(", ");
    410                } else {
    411                    printWriter.print(" }");
    412                }
    413            }
    414        }
    415    }
    416}