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}