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}