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