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      // Needs to be accessible from unit test code
75      static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException {
76          JavaClass javaClass;
77          if ((javaClass = Repository.lookupClass(name)) == null) {
78              javaClass = new ClassParser(name).parse(); // May throw IOException
79          }
80          return javaClass;
81      }
82  
83      /**
84       * Default main method.
85       *
86       * @param argv command line arguments.
87       * @throws Exception if an error occurs.
88       */
89      public static void main(final String[] argv) throws Exception {
90          if (argv.length != 1) {
91              System.out.println("Usage: BCELifier className");
92              System.out.println("\tThe class must exist on the classpath");
93              return;
94          }
95          final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out);
96          bcelifier.start();
97      }
98  
99      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 }