001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.bcel.util; 018 019import java.io.IOException; 020import java.io.OutputStream; 021import java.io.OutputStreamWriter; 022import java.io.PrintWriter; 023import java.nio.charset.StandardCharsets; 024import java.util.Locale; 025 026import org.apache.bcel.Const; 027import org.apache.bcel.Repository; 028import org.apache.bcel.classfile.ClassParser; 029import org.apache.bcel.classfile.Code; 030import org.apache.bcel.classfile.ConstantValue; 031import org.apache.bcel.classfile.ExceptionTable; 032import org.apache.bcel.classfile.Field; 033import org.apache.bcel.classfile.JavaClass; 034import org.apache.bcel.classfile.Method; 035import org.apache.bcel.classfile.StackMap; 036import org.apache.bcel.classfile.StackMapEntry; 037import org.apache.bcel.classfile.StackMapType; 038import org.apache.bcel.classfile.Utility; 039import org.apache.bcel.generic.ArrayType; 040import org.apache.bcel.generic.ConstantPoolGen; 041import org.apache.bcel.generic.MethodGen; 042import org.apache.bcel.generic.Type; 043import org.apache.commons.lang3.ArrayUtils; 044import org.apache.commons.lang3.StringUtils; 045 046/** 047 * This class takes a given JavaClass object and converts it to a Java program that creates that very class using BCEL. 048 * This gives new users of BCEL a useful example showing how things are done with BCEL. It does not cover all features 049 * of BCEL, but tries to mimic hand-written code as close as possible. 050 */ 051public class BCELifier extends org.apache.bcel.classfile.EmptyVisitor { 052 053 /** 054 * Enum corresponding to flag source. 055 */ 056 public enum FLAGS { 057 UNKNOWN, CLASS, METHOD, 058 } 059 060 // The base package name for imports; assumes Const is at the top level 061 // N.B we use the class so renames will be detected by the compiler/IDE 062 private static final String BASE_PACKAGE = Const.class.getPackage().getName(); 063 private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + "."; 064 065 // Needs to be accessible from unit test code 066 static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException { 067 JavaClass javaClass; 068 if ((javaClass = Repository.lookupClass(name)) == null) { 069 javaClass = new ClassParser(name).parse(); // May throw IOException 070 } 071 return javaClass; 072 } 073 074 /** 075 * Default main method 076 */ 077 public static void main(final String[] argv) throws Exception { 078 if (argv.length != 1) { 079 System.out.println("Usage: BCELifier className"); 080 System.out.println("\tThe class must exist on the classpath"); 081 return; 082 } 083 final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out); 084 bcelifier.start(); 085 } 086 087 static String printArgumentTypes(final Type[] argTypes) { 088 if (argTypes.length == 0) { 089 return "Type.NO_ARGS"; 090 } 091 final StringBuilder args = new StringBuilder(); 092 for (int i = 0; i < argTypes.length; i++) { 093 args.append(printType(argTypes[i])); 094 if (i < argTypes.length - 1) { 095 args.append(", "); 096 } 097 } 098 return "new Type[] { " + args.toString() + " }"; 099 } 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}