    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        UNKNOWN, CLASS, METHOD,
    059    }
    060
    061    // The base package name for imports; assumes Const is at the top level
    062    // N.B we use the class so renames will be detected by the compiler/IDE
    063    private static final String BASE_PACKAGE = Const.class.getPackage().getName();
    064    private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + ".";
    065
    066    // Needs to be accessible from unit test code
    067    static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException {
    068        JavaClass javaClass;
    069        if ((javaClass = Repository.lookupClass(name)) == null) {
    070            javaClass = new ClassParser(name).parse(); // May throw IOException
    071        }
    072        return javaClass;
    073    }
    074
    075    /**
    076     * Default main method
    077     */
    078    public static void main(final String[] argv) throws Exception {
    079        if (argv.length != 1) {
    080            System.out.println("Usage: BCELifier className");
    081            System.out.println("\tThe class must exist on the classpath");
    082            return;
    083        }
    084        final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out);
    085        bcelifier.start();
    086    }
    087
    088    static String printArgumentTypes(final Type[] argTypes) {
    089        if (argTypes.length == 0) {
    090            return "Type.NO_ARGS";
    091        }
    092        final StringBuilder args = new StringBuilder();
    093        for (int i = 0; i < argTypes.length; i++) {
    094            args.append(printType(argTypes[i]));
    095            if (i < argTypes.length - 1) {
    096                args.append(", ");
    097            }
    098        }
    099        return "new Type[] { " + args.toString() + " }";
    100    }
    101
    102    static String printFlags(final int flags) {
    103        return printFlags(flags, FLAGS.UNKNOWN);
    104    }
    105
    106    /**
    107     * Return a string with the flag settings
    108     *
    109     * @param flags the flags field to interpret
    110     * @param location the item type
    111     * @return the formatted string
    112     * @since 6.0 made public
    113     */
    114    public static String printFlags(final int flags, final FLAGS location) {
    115        if (flags == 0) {
    116            return "0";
    117        }
    118        final StringBuilder buf = new StringBuilder();
    119        for (int i = 0, pow = 1; pow <= Const.MAX_ACC_FLAG_I; i++) {
    120            if ((flags & pow) != 0) {
    121                if (pow == Const.ACC_SYNCHRONIZED && location == FLAGS.CLASS) {
    122                    buf.append(CONSTANT_PREFIX).append("ACC_SUPER | ");
    123                } else if (pow == Const.ACC_VOLATILE && location == FLAGS.METHOD) {
    124                    buf.append(CONSTANT_PREFIX).append("ACC_BRIDGE | ");
    125                } else if (pow == Const.ACC_TRANSIENT && location == FLAGS.METHOD) {
    126                    buf.append(CONSTANT_PREFIX).append("ACC_VARARGS | ");
    127                } else if (i < Const.ACCESS_NAMES_LENGTH) {
    128                    buf.append(CONSTANT_PREFIX).append("ACC_").append(StringUtils.toRootUpperCase(Const.getAccessName(i))).append(" | ");
    129                } else {
    130                    buf.append(String.format(CONSTANT_PREFIX + "ACC_BIT %x | ", pow));
    131                }
    132            }
    133            pow <<= 1;
    134        }
    135        final String str = buf.toString();
    136        return str.substring(0, str.length() - 3);
    137    }
    138
    139    static String printType(final String signature) {
    140        final Type type = Type.getType(signature);
    141        final byte t = type.getType();
    142        if (t <= Const.T_VOID) {
    143            return "Type." + StringUtils.toRootUpperCase(Const.getTypeName(t));
    144        }
    145        if (type.toString().equals("java.lang.String")) {
    146            return "Type.STRING";
    147        }
    148        if (type.toString().equals("java.lang.Object")) {
    149            return "Type.OBJECT";
    150        }
    151        if (type.toString().equals("java.lang.StringBuffer")) {
    152            return "Type.STRINGBUFFER";
    153        }
    154        if (type instanceof ArrayType) {
    155            final ArrayType at = (ArrayType) type;
    156            return "new ArrayType(" + printType(at.getBasicType()) + ", " + at.getDimensions() + ")";
    157        }
    158        return "new ObjectType(\"" + Utility.signatureToString(signature, false) + "\")";
    159    }
    160
    161    static String printType(final Type type) {
    162        return printType(type.getSignature());
    163    }
    164
    165    private final JavaClass clazz;
    166
    167    private final PrintWriter printWriter;
    168
    169    private final ConstantPoolGen constantPoolGen;
    170
    171    /**
    172     * Constructs a new instance.
    173     *
    174     * @param clazz Java class to "decompile".
    175     * @param out where to print the Java program in UTF-8.
    176     */
    177    public BCELifier(final JavaClass clazz, final OutputStream out) {
    178        this.clazz = clazz;
    179        this.printWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), false);
    180        this.constantPoolGen = new ConstantPoolGen(this.clazz.getConstantPool());
    181    }
    182
    183    private void printCreate() {
    184        printWriter.println("  public void create(OutputStream out) throws IOException {");
    185        final Field[] fields = clazz.getFields();
    186        if (fields.length > 0) {
    187            printWriter.println("    createFields();");
    188        }
    189        final Method[] methods = clazz.getMethods();
    190        for (int i = 0; i < methods.length; i++) {
    191            printWriter.println("    createMethod_" + i + "();");
    192        }
    193        printWriter.println("    _cg.getJavaClass().dump(out);");
    194        printWriter.println("  }");
    195        printWriter.println();
    196    }
    197
    198    private void printMain() {
    199        final String className = clazz.getClassName();
    200        printWriter.println("  public static void main(String[] args) throws Exception {");
    201        printWriter.println("    " + className + "Creator creator = new " + className + "Creator();");
    202        printWriter.println("    creator.create(new FileOutputStream(\"" + className + ".class\"));");
    203        printWriter.println("  }");
    204    }
    205
    206    /**
    207     * Start Java code generation
    208     */
    209    public void start() {
    210        visitJavaClass(clazz);
    211        printWriter.flush();
    212    }
    213
    214    @Override
    215    public void visitField(final Field field) {
    216        printWriter.println();
    217        printWriter.println(
    218            "    field = new FieldGen(" + printFlags(field.getAccessFlags()) + ", " + printType(field.getSignature()) + ", \"" + field.getName() + "\", _cp);");
    219        final ConstantValue cv = field.getConstantValue();
    220        if (cv != null) {
    221            printWriter.print("    field.setInitValue(");
    222            if (field.getType() == Type.CHAR) {
    223                printWriter.print("(char)");
    224            }
    225            if (field.getType() == Type.SHORT) {
    226                printWriter.print("(short)");
    227            }
    228            if (field.getType() == Type.BYTE) {
    229                printWriter.print("(byte)");
    230            }
    231            printWriter.print(cv);
    232            if (field.getType() == Type.LONG) {
    233                printWriter.print("L");
    234            }
    235            if (field.getType() == Type.FLOAT) {
    236                printWriter.print("F");
    237            }
    238            if (field.getType() == Type.DOUBLE) {
    239                printWriter.print("D");
    240            }
    241            printWriter.println(");");
    242        }
    243        printWriter.println("    _cg.addField(field.getField());");
    244    }
    245
    246    @Override
    247    public void visitJavaClass(final JavaClass clazz) {
    248        String className = clazz.getClassName();
    249        final String superName = clazz.getSuperclassName();
    250        final String packageName = clazz.getPackageName();
    251        final String inter = Utility.printArray(clazz.getInterfaceNames(), false, true);
    252        if (StringUtils.isNotEmpty(packageName)) {
    253            className = className.substring(packageName.length() + 1);
    254            printWriter.println("package " + packageName + ";");
    255            printWriter.println();
    256        }
    257        printWriter.println("import " + BASE_PACKAGE + ".generic.*;");
    258        printWriter.println("import " + BASE_PACKAGE + ".classfile.*;");
    259        printWriter.println("import " + BASE_PACKAGE + ".*;");
    260        printWriter.println("import java.io.*;");
    261        printWriter.println();
    262        printWriter.println("public class " + className + "Creator {");
    263        printWriter.println("  private InstructionFactory _factory;");
    264        printWriter.println("  private ConstantPoolGen    _cp;");
    265        printWriter.println("  private ClassGen           _cg;");
    266        printWriter.println();
    267        printWriter.println("  public " + className + "Creator() {");
    268        printWriter.println("    _cg = new ClassGen(\"" + (packageName.isEmpty() ? className : packageName + "." + className) + "\", \"" + superName
    269            + "\", \"" + clazz.getSourceFileName() + "\", " + printFlags(clazz.getAccessFlags(), FLAGS.CLASS) + ", " + "new String[] { " + inter + " });");
    270        printWriter.println("    _cg.setMajor(" + clazz.getMajor() + ");");
    271        printWriter.println("    _cg.setMinor(" + clazz.getMinor() + ");");
    272        printWriter.println();
    273        printWriter.println("    _cp = _cg.getConstantPool();");
    274        printWriter.println("    _factory = new InstructionFactory(_cg, _cp);");
    275        printWriter.println("  }");
    276        printWriter.println();
    277        printCreate();
    278        final Field[] fields = clazz.getFields();
    279        if (fields.length > 0) {
    280            printWriter.println("  private void createFields() {");
    281            printWriter.println("    FieldGen field;");
    282            for (final Field field : fields) {
    283                field.accept(this);
    284            }
    285            printWriter.println("  }");
    286            printWriter.println();
    287        }
    288        final Method[] methods = clazz.getMethods();
    289        for (int i = 0; i < methods.length; i++) {
    290            printWriter.println("  private void createMethod_" + i + "() {");
    291            methods[i].accept(this);
    292            printWriter.println("  }");
    293            printWriter.println();
    294        }
    295        printMain();
    296        printWriter.println("}");
    297    }
    298
    299    @Override
    300    public void visitMethod(final Method method) {
    301        final MethodGen mg = new MethodGen(method, clazz.getClassName(), constantPoolGen);
    302        printWriter.println("    InstructionList il = new InstructionList();");
    303        printWriter.println("    MethodGen method = new MethodGen(" + printFlags(method.getAccessFlags(), FLAGS.METHOD) + ", " + printType(mg.getReturnType())
    304            + ", " + printArgumentTypes(mg.getArgumentTypes()) + ", new String[] { " + Utility.printArray(mg.getArgumentNames(), false, true) + " }, \""
    305            + method.getName() + "\", \"" + clazz.getClassName() + "\", il, _cp);");
    306        final ExceptionTable exceptionTable = method.getExceptionTable();
    307        if (exceptionTable != null) {
    308            final String[] exceptionNames = exceptionTable.getExceptionNames();
    309            for (final String exceptionName : exceptionNames) {
    310                printWriter.print("    method.addException(\"");
    311                printWriter.print(exceptionName);
    312                printWriter.println("\");");
    313            }
    314        }
    315        final Code code = method.getCode();
    316        if (code != null) {
    317            final StackMap stackMap = code.getStackMap();
    318            if (stackMap != null) {
    319                stackMap.accept(this);
    320            }
    321        }
    322        printWriter.println();
    323        final BCELFactory factory = new BCELFactory(mg, printWriter);
    324        factory.start();
    325        printWriter.println("    method.setMaxStack();");
    326        printWriter.println("    method.setMaxLocals();");
    327        printWriter.println("    _cg.addMethod(method.getMethod());");
    328        printWriter.println("    il.dispose();");
    329    }
    330
    331    @Override
    332    public void visitStackMap(final StackMap stackMap) {
    333        super.visitStackMap(stackMap);
    334        printWriter.print("    method.addCodeAttribute(");
    335        printWriter.print("new StackMap(_cp.addUtf8(\"");
    336        printWriter.print(stackMap.getName());
    337        printWriter.print("\"), ");
    338        printWriter.print(stackMap.getLength());
    339        printWriter.print(", ");
    340        printWriter.print("new StackMapEntry[] {");
    341        final StackMapEntry[] table = stackMap.getStackMap();
    342        for (int i = 0; i < table.length; i++) {
    343            table[i].accept(this);
    344            if (i < table.length - 1) {
    345                printWriter.print(", ");
    346            } else {
    347                printWriter.print(" }");
    348            }
    349        }
    350        printWriter.print(", _cp.getConstantPool())");
    351        printWriter.println(");");
    352    }
    353
    354    @Override
    355    public void visitStackMapEntry(final StackMapEntry stackMapEntry) {
    356        super.visitStackMapEntry(stackMapEntry);
    357        printWriter.print("new StackMapEntry(");
    358        printWriter.print(stackMapEntry.getFrameType());
    359        printWriter.print(", ");
    360        printWriter.print(stackMapEntry.getByteCodeOffset());
    361        printWriter.print(", ");
    362        visitStackMapTypeArray(stackMapEntry.getTypesOfLocals());
    363        printWriter.print(", ");
    364        visitStackMapTypeArray(stackMapEntry.getTypesOfStackItems());
    365        printWriter.print(", _cp.getConstantPool())");
    366    }
    367
    368    /**
    369     * Visits a {@link StackMapType} object.
    370     * @param stackMapType object to visit
    371     * @since 6.7.1
    372     */
    373    @Override
    374    public void visitStackMapType(final StackMapType stackMapType) {
    375        super.visitStackMapType(stackMapType);
    376        printWriter.print("new StackMapType((byte)");
    377        printWriter.print(stackMapType.getType());
    378        printWriter.print(", ");
    379        if (stackMapType.hasIndex()) {
    380            printWriter.print("_cp.addClass(\"");
    381            printWriter.print(stackMapType.getClassName());
    382            printWriter.print("\")");
    383        } else {
    384            printWriter.print("-1");
    385        }
    386        printWriter.print(", _cp.getConstantPool())");
    387    }
    388
    389    private void visitStackMapTypeArray(final StackMapType[] types) {
    390        if (ArrayUtils.isEmpty(types)) {
    391            printWriter.print("null"); // null translates to StackMapType.EMPTY_ARRAY
    392        } else {
    393            printWriter.print("new StackMapType[] {");
    394            for (int i = 0; i < types.length; i++) {
    395                types[i].accept(this);
    396                if (i < types.length - 1) {
    397                    printWriter.print(", ");
    398                } else {
    399                    printWriter.print(" }");
    400                }
    401            }
    402        }
    403    }
    404}