View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.bcel.util;
18  
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.io.OutputStreamWriter;
22  import java.io.PrintWriter;
23  import java.nio.charset.StandardCharsets;
24  import java.util.Locale;
25  
26  import org.apache.bcel.Const;
27  import org.apache.bcel.Repository;
28  import org.apache.bcel.classfile.ClassParser;
29  import org.apache.bcel.classfile.Code;
30  import org.apache.bcel.classfile.ConstantValue;
31  import org.apache.bcel.classfile.ExceptionTable;
32  import org.apache.bcel.classfile.Field;
33  import org.apache.bcel.classfile.JavaClass;
34  import org.apache.bcel.classfile.Method;
35  import org.apache.bcel.classfile.StackMap;
36  import org.apache.bcel.classfile.StackMapEntry;
37  import org.apache.bcel.classfile.StackMapType;
38  import org.apache.bcel.classfile.Utility;
39  import org.apache.bcel.generic.ArrayType;
40  import org.apache.bcel.generic.ConstantPoolGen;
41  import org.apache.bcel.generic.MethodGen;
42  import org.apache.bcel.generic.Type;
43  import org.apache.commons.lang3.ArrayUtils;
44  import org.apache.commons.lang3.StringUtils;
45  
46  /**
47   * This class takes a given JavaClass object and converts it to a Java program that creates that very class using BCEL.
48   * This gives new users of BCEL a useful example showing how things are done with BCEL. It does not cover all features
49   * of BCEL, but tries to mimic hand-written code as close as possible.
50   */
51  public class BCELifier extends org.apache.bcel.classfile.EmptyVisitor {
52  
53      /**
54       * Enum corresponding to flag source.
55       */
56      public enum FLAGS {
57          UNKNOWN, CLASS, METHOD,
58      }
59  
60      // The base package name for imports; assumes Const is at the top level
61      // N.B we use the class so renames will be detected by the compiler/IDE
62      private static final String BASE_PACKAGE = Const.class.getPackage().getName();
63      private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + ".";
64  
65      // Needs to be accessible from unit test code
66      static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException {
67          JavaClass javaClass;
68          if ((javaClass = Repository.lookupClass(name)) == null) {
69              javaClass = new ClassParser(name).parse(); // May throw IOException
70          }
71          return javaClass;
72      }
73  
74      /**
75       * Default main method
76       */
77      public static void main(final String[] argv) throws Exception {
78          if (argv.length != 1) {
79              System.out.println("Usage: BCELifier className");
80              System.out.println("\tThe class must exist on the classpath");
81              return;
82          }
83          final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out);
84          bcelifier.start();
85      }
86  
87      static String printArgumentTypes(final Type[] argTypes) {
88          if (argTypes.length == 0) {
89              return "Type.NO_ARGS";
90          }
91          final StringBuilder args = new StringBuilder();
92          for (int i = 0; i < argTypes.length; i++) {
93              args.append(printType(argTypes[i]));
94              if (i < argTypes.length - 1) {
95                  args.append(", ");
96              }
97          }
98          return "new Type[] { " + args.toString() + " }";
99      }
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 }