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 059 /** Unknown flag source. */ 060 UNKNOWN, 061 062 /** Class flag source. */ 063 CLASS, 064 065 /** Method flag source. */ 066 METHOD, 067 } 068 069 // The base package name for imports; assumes Const is at the top level 070 // N.B we use the class so renames will be detected by the compiler/IDE 071 private static final String BASE_PACKAGE = Const.class.getPackage().getName(); 072 private static final String CONSTANT_PREFIX = Const.class.getSimpleName() + "."; 073 074 /** 075 * Checks that a name from the parsed class file is a dotted sequence of valid Java identifiers before it is 076 * emitted in identifier position of the generated source. The class file format allows characters in names (for 077 * example braces, parentheses or newlines) that the Java language does not, so an unchecked name from a crafted 078 * class file could inject arbitrary code into the generated program. 079 * 080 * @param name the class or package name to check. 081 * @return {@code name} if it is safe to emit as a Java identifier. 082 * @throws IllegalArgumentException Thrown if the name is not a dotted sequence of valid Java identifiers. 083 */ 084 private static String checkJavaName(final String name) { 085 boolean expectStart = true; 086 for (int i = 0; i < name.length(); i++) { 087 final char ch = name.charAt(i); 088 if (expectStart) { 089 if (!Character.isJavaIdentifierStart(ch)) { 090 throw new IllegalArgumentException("Invalid Java identifier in class file: " + Utility.convertString(name)); 091 } 092 expectStart = false; 093 } else if (ch == '.') { 094 expectStart = true; 095 } else if (!Character.isJavaIdentifierPart(ch)) { 096 throw new IllegalArgumentException("Invalid Java identifier in class file: " + Utility.convertString(name)); 097 } 098 } 099 if (expectStart) { 100 throw new IllegalArgumentException("Invalid Java identifier in class file: " + Utility.convertString(name)); 101 } 102 return name; 103 } 104 105 private static String[] escape(final String[] names) { 106 if (names == null) { 107 return null; 108 } 109 final String[] escaped = new String[names.length]; 110 for (int i = 0; i < names.length; i++) { 111 escaped[i] = names[i] == null ? null : Utility.convertString(names[i]); 112 } 113 return escaped; 114 } 115 116 // Needs to be accessible from unit test code 117 static JavaClass getJavaClass(final String name) throws ClassNotFoundException, IOException { 118 JavaClass javaClass; 119 if ((javaClass = Repository.lookupClass(name)) == null) { 120 javaClass = new ClassParser(name).parse(); // May throw IOException 121 } 122 return javaClass; 123 } 124 125 /** 126 * Default main method. 127 * 128 * @param argv command line arguments. 129 * @throws Exception Thrown if an error occurs. 130 */ 131 public static void main(final String[] argv) throws Exception { 132 if (argv.length != 1) { 133 System.out.println("Usage: BCELifier className"); 134 System.out.println("\tThe class must exist on the classpath"); 135 return; 136 } 137 final BCELifier bcelifier = new BCELifier(getJavaClass(argv[0]), System.out); 138 bcelifier.start(); 139 } 140 141 static String printArgumentTypes(final Type[] argTypes) { 142 if (argTypes.length == 0) { 143 return "Type.NO_ARGS"; 144 } 145 final StringBuilder args = new StringBuilder(); 146 for (int i = 0; i < argTypes.length; i++) { 147 args.append(printType(argTypes[i])); 148 if (i < argTypes.length - 1) { 149 args.append(", "); 150 } 151 } 152 return "new Type[] { " + args.toString() + " }"; 153 } 154 155 static String printFlags(final int flags) { 156 return printFlags(flags, FLAGS.UNKNOWN); 157 } 158 159 /** 160 * Return a string with the flag settings 161 * 162 * @param flags The flags field to interpret. 163 * @param location The item type. 164 * @return The formatted string. 165 * @since 6.0 made public 166 */ 167 public static String printFlags(final int flags, final FLAGS location) { 168 if (flags == 0) { 169 return "0"; 170 } 171 final StringBuilder buf = new StringBuilder(); 172 for (int i = 0, pow = 1; pow <= Const.MAX_ACC_FLAG_I; i++) { 173 if ((flags & pow) != 0) { 174 if (pow == Const.ACC_SYNCHRONIZED && location == FLAGS.CLASS) { 175 buf.append(CONSTANT_PREFIX).append("ACC_SUPER | "); 176 } else if (pow == Const.ACC_VOLATILE && location == FLAGS.METHOD) { 177 buf.append(CONSTANT_PREFIX).append("ACC_BRIDGE | "); 178 } else if (pow == Const.ACC_TRANSIENT && location == FLAGS.METHOD) { 179 buf.append(CONSTANT_PREFIX).append("ACC_VARARGS | "); 180 } else if (i < Const.ACCESS_NAMES_LENGTH) { 181 buf.append(CONSTANT_PREFIX).append("ACC_").append(StringUtils.toRootUpperCase(Const.getAccessName(i))).append(" | "); 182 } else { 183 buf.append(String.format(CONSTANT_PREFIX + "ACC_BIT %x | ", pow)); 184 } 185 } 186 pow <<= 1; 187 } 188 final String str = buf.toString(); 189 return str.substring(0, str.length() - 3); 190 } 191 192 static String printType(final String signature) { 193 final Type type = Type.getType(signature); 194 final byte t = type.getType(); 195 if (t <= Const.T_VOID) { 196 return "Type." + StringUtils.toRootUpperCase(Const.getTypeName(t)); 197 } 198 if (type.toString().equals("java.lang.String")) { 199 return "Type.STRING"; 200 } 201 if (type.toString().equals("java.lang.Object")) { 202 return "Type.OBJECT"; 203 } 204 if (type.toString().equals("java.lang.StringBuffer")) { 205 return "Type.STRINGBUFFER"; 206 } 207 if (type instanceof ArrayType) { 208 final ArrayType at = (ArrayType) type; 209 return "new ArrayType(" + printType(at.getBasicType()) + ", " + at.getDimensions() + ")"; 210 } 211 return "new ObjectType(\"" + Utility.signatureToString(signature, false) + "\")"; 212 } 213 214 static String printType(final Type type) { 215 return printType(type.getSignature()); 216 } 217 218 private final JavaClass clazz; 219 220 private final PrintWriter printWriter; 221 222 private final ConstantPoolGen constantPoolGen; 223 224 /** 225 * Constructs a new instance. 226 * 227 * @param clazz Java class to "decompile". 228 * @param out where to print the Java program in UTF-8. 229 */ 230 public BCELifier(final JavaClass clazz, final OutputStream out) { 231 this.clazz = clazz; 232 this.printWriter = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), false); 233 this.constantPoolGen = new ConstantPoolGen(this.clazz.getConstantPool()); 234 } 235 236 private void printCreate() { 237 printWriter.println(" public void create(OutputStream out) throws IOException {"); 238 final Field[] fields = clazz.getFields(); 239 if (fields.length > 0) { 240 printWriter.println(" createFields();"); 241 } 242 final Method[] methods = clazz.getMethods(); 243 for (int i = 0; i < methods.length; i++) { 244 printWriter.println(" createMethod_" + i + "();"); 245 } 246 printWriter.println(" _cg.getJavaClass().dump(out);"); 247 printWriter.println(" }"); 248 printWriter.println(); 249 } 250 251 private void printMain() { 252 final String className = checkJavaName(clazz.getClassName()); 253 printWriter.println(" public static void main(String[] args) throws Exception {"); 254 printWriter.println(" " + className + "Creator creator = new " + className + "Creator();"); 255 printWriter.println(" creator.create(new FileOutputStream(\"" + Utility.convertString(className) + ".class\"));"); 256 printWriter.println(" }"); 257 } 258 259 /** 260 * Start Java code generation 261 */ 262 public void start() { 263 visitJavaClass(clazz); 264 printWriter.flush(); 265 } 266 267 @Override 268 public void visitField(final Field field) { 269 printWriter.println(); 270 printWriter.println(" field = new FieldGen(" + printFlags(field.getAccessFlags()) + ", " + printType(field.getSignature()) + ", \"" 271 + Utility.convertString(field.getName()) + "\", _cp);"); 272 final ConstantValue cv = field.getConstantValue(); 273 if (cv != null) { 274 printWriter.print(" field.setInitValue("); 275 if (field.getType() == Type.CHAR) { 276 printWriter.print("(char)"); 277 } 278 if (field.getType() == Type.SHORT) { 279 printWriter.print("(short)"); 280 } 281 if (field.getType() == Type.BYTE) { 282 printWriter.print("(byte)"); 283 } 284 printWriter.print(cv); 285 if (field.getType() == Type.LONG) { 286 printWriter.print("L"); 287 } 288 if (field.getType() == Type.FLOAT) { 289 printWriter.print("F"); 290 } 291 if (field.getType() == Type.DOUBLE) { 292 printWriter.print("D"); 293 } 294 printWriter.println(");"); 295 } 296 printWriter.println(" _cg.addField(field.getField());"); 297 } 298 299 @Override 300 public void visitJavaClass(final JavaClass clazz) { 301 String className = checkJavaName(clazz.getClassName()); 302 final String superName = clazz.getSuperclassName(); 303 final String packageName = clazz.getPackageName(); 304 final String inter = Utility.printArray(escape(clazz.getInterfaceNames()), false, true); 305 if (StringUtils.isNotEmpty(packageName)) { 306 className = className.substring(packageName.length() + 1); 307 printWriter.println("package " + packageName + ";"); 308 printWriter.println(); 309 } 310 printWriter.println("import " + BASE_PACKAGE + ".generic.*;"); 311 printWriter.println("import " + BASE_PACKAGE + ".classfile.*;"); 312 printWriter.println("import " + BASE_PACKAGE + ".*;"); 313 printWriter.println("import java.io.*;"); 314 printWriter.println(); 315 printWriter.println("public class " + className + "Creator {"); 316 printWriter.println(" private InstructionFactory _factory;"); 317 printWriter.println(" private ConstantPoolGen _cp;"); 318 printWriter.println(" private ClassGen _cg;"); 319 printWriter.println(); 320 printWriter.println(" public " + className + "Creator() {"); 321 printWriter.println(" _cg = new ClassGen(\"" + Utility.convertString(packageName.isEmpty() ? className : packageName + "." + className) + "\", \"" 322 + Utility.convertString(superName) + "\", \"" + Utility.convertString(clazz.getSourceFileName()) + "\", " 323 + printFlags(clazz.getAccessFlags(), FLAGS.CLASS) + ", " + "new String[] { " + inter + " });"); 324 printWriter.println(" _cg.setMajor(" + clazz.getMajor() + ");"); 325 printWriter.println(" _cg.setMinor(" + clazz.getMinor() + ");"); 326 printWriter.println(); 327 printWriter.println(" _cp = _cg.getConstantPool();"); 328 printWriter.println(" _factory = new InstructionFactory(_cg, _cp);"); 329 printWriter.println(" }"); 330 printWriter.println(); 331 printCreate(); 332 final Field[] fields = clazz.getFields(); 333 if (fields.length > 0) { 334 printWriter.println(" private void createFields() {"); 335 printWriter.println(" FieldGen field;"); 336 for (final Field field : fields) { 337 field.accept(this); 338 } 339 printWriter.println(" }"); 340 printWriter.println(); 341 } 342 final Method[] methods = clazz.getMethods(); 343 for (int i = 0; i < methods.length; i++) { 344 printWriter.println(" private void createMethod_" + i + "() {"); 345 methods[i].accept(this); 346 printWriter.println(" }"); 347 printWriter.println(); 348 } 349 printMain(); 350 printWriter.println("}"); 351 } 352 353 @Override 354 public void visitMethod(final Method method) { 355 final MethodGen mg = new MethodGen(method, clazz.getClassName(), constantPoolGen); 356 printWriter.println(" InstructionList il = new InstructionList();"); 357 printWriter.println(" MethodGen method = new MethodGen(" + printFlags(method.getAccessFlags(), FLAGS.METHOD) + ", " + printType(mg.getReturnType()) 358 + ", " + printArgumentTypes(mg.getArgumentTypes()) + ", new String[] { " + Utility.printArray(escape(mg.getArgumentNames()), false, true) + " }, \"" 359 + Utility.convertString(method.getName()) + "\", \"" + Utility.convertString(clazz.getClassName()) + "\", il, _cp);"); 360 final ExceptionTable exceptionTable = method.getExceptionTable(); 361 if (exceptionTable != null) { 362 final String[] exceptionNames = exceptionTable.getExceptionNames(); 363 for (final String exceptionName : exceptionNames) { 364 printWriter.print(" method.addException(\""); 365 printWriter.print(Utility.convertString(exceptionName)); 366 printWriter.println("\");"); 367 } 368 } 369 final Code code = method.getCode(); 370 if (code != null) { 371 final StackMap stackMap = code.getStackMap(); 372 if (stackMap != null) { 373 stackMap.accept(this); 374 } 375 } 376 printWriter.println(); 377 final BCELFactory factory = new BCELFactory(mg, printWriter); 378 factory.start(); 379 printWriter.println(" method.setMaxStack();"); 380 printWriter.println(" method.setMaxLocals();"); 381 printWriter.println(" _cg.addMethod(method.getMethod());"); 382 printWriter.println(" il.dispose();"); 383 } 384 385 @Override 386 public void visitStackMap(final StackMap stackMap) { 387 super.visitStackMap(stackMap); 388 printWriter.print(" method.addCodeAttribute("); 389 printWriter.print("new StackMap(_cp.addUtf8(\""); 390 printWriter.print(Utility.convertString(stackMap.getName())); 391 printWriter.print("\"), "); 392 printWriter.print(stackMap.getLength()); 393 printWriter.print(", "); 394 printWriter.print("new StackMapEntry[] {"); 395 final StackMapEntry[] table = stackMap.getStackMap(); 396 for (int i = 0; i < table.length; i++) { 397 table[i].accept(this); 398 if (i < table.length - 1) { 399 printWriter.print(", "); 400 } else { 401 printWriter.print(" }"); 402 } 403 } 404 printWriter.print(", _cp.getConstantPool())"); 405 printWriter.println(");"); 406 } 407 408 @Override 409 public void visitStackMapEntry(final StackMapEntry stackMapEntry) { 410 super.visitStackMapEntry(stackMapEntry); 411 printWriter.print("new StackMapEntry("); 412 printWriter.print(stackMapEntry.getFrameType()); 413 printWriter.print(", "); 414 printWriter.print(stackMapEntry.getByteCodeOffset()); 415 printWriter.print(", "); 416 visitStackMapTypeArray(stackMapEntry.getTypesOfLocals()); 417 printWriter.print(", "); 418 visitStackMapTypeArray(stackMapEntry.getTypesOfStackItems()); 419 printWriter.print(", _cp.getConstantPool())"); 420 } 421 422 /** 423 * Visits a {@link StackMapType} object. 424 * 425 * @param stackMapType object to visit. 426 * @since 6.7.1 427 */ 428 @Override 429 public void visitStackMapType(final StackMapType stackMapType) { 430 super.visitStackMapType(stackMapType); 431 printWriter.print("new StackMapType((byte)"); 432 printWriter.print(stackMapType.getType()); 433 printWriter.print(", "); 434 if (stackMapType.hasIndex()) { 435 printWriter.print("_cp.addClass(\""); 436 printWriter.print(Utility.convertString(stackMapType.getClassName())); 437 printWriter.print("\")"); 438 } else { 439 printWriter.print("-1"); 440 } 441 printWriter.print(", _cp.getConstantPool())"); 442 } 443 444 private void visitStackMapTypeArray(final StackMapType[] types) { 445 if (ArrayUtils.isEmpty(types)) { 446 printWriter.print("null"); // null translates to StackMapType.EMPTY_ARRAY 447 } else { 448 printWriter.print("new StackMapType[] {"); 449 for (int i = 0; i < types.length; i++) { 450 types[i].accept(this); 451 if (i < types.length - 1) { 452 printWriter.print(", "); 453 } else { 454 printWriter.print(" }"); 455 } 456 } 457 } 458 } 459}