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