View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.util;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.io.OutputStreamWriter;
24  import java.io.PrintWriter;
25  import java.nio.charset.StandardCharsets;
26  
27  import org.apache.bcel.Const;
28  import org.apache.bcel.Repository;
29  import org.apache.bcel.classfile.ClassParser;
30  import org.apache.bcel.classfile.Code;
31  import org.apache.bcel.classfile.ConstantValue;
32  import org.apache.bcel.classfile.ExceptionTable;
33  import org.apache.bcel.classfile.Field;
34  import org.apache.bcel.classfile.JavaClass;
35  import org.apache.bcel.classfile.Method;
36  import org.apache.bcel.classfile.StackMap;
37  import org.apache.bcel.classfile.StackMapEntry;
38  import org.apache.bcel.classfile.StackMapType;
39  import org.apache.bcel.classfile.Utility;
40  import org.apache.bcel.generic.ArrayType;
41  import org.apache.bcel.generic.ConstantPoolGen;
42  import org.apache.bcel.generic.MethodGen;
43  import org.apache.bcel.generic.Type;
44  import org.apache.commons.lang3.ArrayUtils;
45  import org.apache.commons.lang3.StringUtils;
46  
47  /**
48   * This class takes a given JavaClass object and converts it to a Java program that creates that very class using BCEL.
49   * This gives new users of BCEL a useful example showing how things are done with BCEL. It does not cover all features
50   * of BCEL, but tries to mimic hand-written code as close as possible.
51   */
52  public class BCELifier extends org.apache.bcel.classfile.EmptyVisitor {
53  
54      /**
55       * Enum corresponding to flag source.
56       */
57      public enum FLAGS {
58  
59          /** Unknown flag source. */
60          UNKNOWN,
61  
62          /** Class flag source. */
63          CLASS,
64  
65          /** Method flag source. */
66          METHOD,
67      }
68  
69      // The base package name for imports; assumes Const is at the top level
70      // N.B we use the class so renames will be detected by the compiler/IDE
71      private static final String BASE_PACKAGE = Const.class.getPackage().getName();
72      private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + ".";
73  
74      /**
75       * Checks that a name from the parsed class file is a dotted sequence of valid Java identifiers before it is
76       * emitted in identifier position of the generated source. The class file format allows characters in names (for
77       * example braces, parentheses or newlines) that the Java language does not, so an unchecked name from a crafted
78       * class file could inject arbitrary code into the generated program.
79       *
80       * @param name the class or package name to check.
81       * @return {@code name} if it is safe to emit as a Java identifier.
82       * @throws IllegalArgumentException Thrown if the name is not a dotted sequence of valid Java identifiers.
83       */
84      private static String checkJavaName(final String name) {
85          boolean expectStart = true;
86          for (int i = 0; i < name.length(); i++) {
87              final char ch = name.charAt(i);
88              if (expectStart) {
89                  if (!Character.isJavaIdentifierStart(ch)) {
90                      throw new IllegalArgumentException("Invalid Java identifier in class file: " + Utility.convertString(name));
91                  }
92                  expectStart = false;
93              } else if (ch == '.') {
94                  expectStart = true;
95              } else if (!Character.isJavaIdentifierPart(ch)) {
96                  throw new IllegalArgumentException("Invalid Java identifier in class file: " + Utility.convertString(name));
97              }
98          }
99          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 }