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          UNKNOWN, CLASS, METHOD,
59      }
60  
61      // The base package name for imports; assumes Const is at the top level
62      // N.B we use the class so renames will be detected by the compiler/IDE
63      private static final String BASE_PACKAGE = Const.class.getPackage().getName();
64      private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + ".";
65  
66      // Needs to be accessible from unit test code
67      static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException {
68          JavaClass javaClass;
69          if ((javaClass = Repository.lookupClass(name)) == null) {
70              javaClass = new ClassParser(name).parse(); // May throw IOException
71          }
72          return javaClass;
73      }
74  
75      /**
76       * Default main method
77       */
78      public static void main(final String[] argv) throws Exception {
79          if (argv.length != 1) {
80              System.out.println("Usage: BCELifier className");
81              System.out.println("\tThe class must exist on the classpath");
82              return;
83          }
84          final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out);
85          bcelifier.start();
86      }
87  
88      static String printArgumentTypes(final Type[] argTypes) {
89          if (argTypes.length == 0) {
90              return "Type.NO_ARGS";
91          }
92          final StringBuilder args = new StringBuilder();
93          for (int i = 0; i < argTypes.length; i++) {
94              args.append(printType(argTypes[i]));
95              if (i < argTypes.length - 1) {
96                  args.append(", ");
97              }
98          }
99          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 }