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}