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