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 */ 019 020package org.apache.bcel.classfile; 021 022import java.io.ByteArrayInputStream; 023import java.io.ByteArrayOutputStream; 024import java.io.CharArrayReader; 025import java.io.CharArrayWriter; 026import java.io.FilterReader; 027import java.io.FilterWriter; 028import java.io.IOException; 029import java.io.PrintStream; 030import java.io.PrintWriter; 031import java.io.Reader; 032import java.io.Writer; 033import java.util.ArrayList; 034import java.util.Arrays; 035import java.util.List; 036import java.util.zip.GZIPInputStream; 037import java.util.zip.GZIPOutputStream; 038 039import org.apache.bcel.Const; 040import org.apache.bcel.util.ByteSequence; 041import org.apache.commons.io.IOUtils; 042import org.apache.commons.io.input.BoundedInputStream; 043import org.apache.commons.lang3.ArrayFill; 044import org.apache.commons.lang3.ArrayUtils; 045import org.apache.commons.lang3.StringUtils; 046 047/** 048 * Utility functions that do not really belong to any class in particular. 049 */ 050// @since 6.0 methods are no longer final 051public abstract class Utility { 052 053 /** 054 * Decode characters into bytes. Used by <a href="Utility.html#decode(java.lang.String, boolean)">decode()</a> 055 */ 056 private static final class JavaReader extends FilterReader { 057 058 JavaReader(final Reader in) { 059 super(in); 060 } 061 062 @Override 063 public int read() throws IOException { 064 final int b = in.read(); 065 if (b != ESCAPE_CHAR) { 066 return b; 067 } 068 final int i = in.read(); 069 if (i < 0) { 070 return -1; 071 } 072 if (isHex(i)) { // Normal escape 073 final int j = in.read(); 074 if (j < 0) { 075 return -1; 076 } 077 if (!isHex(j)) { 078 // Would otherwise reach Integer.parseInt and throw an undeclared NumberFormatException. 079 throw new IOException("Invalid escape sequence: expected a second hexadecimal digit after '" + ESCAPE_CHAR + (char) i + "'"); 080 } 081 final char[] tmp = { (char) i, (char) j }; 082 return Integer.parseInt(new String(tmp), 16); 083 } 084 if (i >= MAP_CHAR.length || MAP_CHAR[i] == UNMAPPED) { 085 // Reject instead of throwing an undeclared ArrayIndexOutOfBoundsException (i >= 256) or silently 086 // aliasing every unmapped character to MAP_CHAR's default slot value (the '$A' encoding). 087 throw new IOException("Invalid escape character after '" + ESCAPE_CHAR + "': 0x" + Integer.toHexString(i)); 088 } 089 return MAP_CHAR[i]; 090 } 091 092 @Override 093 public int read(final char[] cbuf, final int off, final int len) throws IOException { 094 for (int i = 0; i < len; i++) { 095 final int ch = read(); 096 if (ch < 0) { 097 // Propagate end-of-stream instead of writing (char) -1 and over-reporting the read length. 098 return i > 0 ? i : -1; 099 } 100 cbuf[off + i] = (char) ch; 101 } 102 return len; 103 } 104 } 105 106 /** 107 * Encode bytes into valid Java identifier characters. Used by 108 * <a href="Utility.html#encode(byte[], boolean)">encode()</a> 109 */ 110 private static final class JavaWriter extends FilterWriter { 111 112 JavaWriter(final Writer out) { 113 super(out); 114 } 115 116 @Override 117 public void write(final char[] cbuf, final int off, final int len) throws IOException { 118 for (int i = 0; i < len; i++) { 119 write(cbuf[off + i]); 120 } 121 } 122 123 @Override 124 public void write(final int b) throws IOException { 125 if (isJavaIdentifierPart((char) b) && b != ESCAPE_CHAR) { 126 out.write(b); 127 } else { 128 out.write(ESCAPE_CHAR); // Escape character 129 // Special escape 130 if (b >= 0 && b < FREE_CHARS) { 131 out.write(CHAR_MAP[b]); 132 } else { // Normal escape 133 final char[] tmp = Integer.toHexString(b).toCharArray(); 134 if (tmp.length == 1) { 135 out.write('0'); 136 out.write(tmp[0]); 137 } else { 138 out.write(tmp[0]); 139 out.write(tmp[1]); 140 } 141 } 142 } 143 } 144 145 @Override 146 public void write(final String str, final int off, final int len) throws IOException { 147 write(str.toCharArray(), off, len); 148 } 149 } 150 151 /* 152 * Maximum nesting depth accepted by typeSignatureToString(). Signatures are attacker-controlled bytes from untrusted class files; without a limit, a deeply 153 * nested generic signature such as "LA<LA<LA<...>;>;>;" drives one stack frame per nesting level and kills the calling thread with a StackOverflowError. 154 */ 155 private static final int MAX_SIGNATURE_NESTING = 512; 156 157 /** 158 * The maximum number of bytes that {@link #decode(String, boolean)} will decompress. Guards against decompression bombs: the compressed input is 159 * attacker-controlled and a small input can decompress to an enormous size. 160 */ 161 private static final int MAX_DECODED_LENGTH = 64 * 1024 * 1024; 162 163 /** Marker for {@link #MAP_CHAR} slots that do not correspond to a valid special escape character. */ 164 private static final int UNMAPPED = -1; 165 166 /* 167 * How many chars have been consumed during parsing in typeSignatureToString(). Read by methodSignatureToString(). Set 168 * by side effect, but only internally. 169 */ 170 private static final ThreadLocal<Integer> CONSUMER_CHARS = ThreadLocal.withInitial(() -> Integer.valueOf(0)); 171 172 /* 173 * The 'WIDE' instruction is used in the byte code to allow 16-bit wide indices for local variables. This opcode 174 * precedes an 'ILOAD', for example. The opcode immediately following takes an extra byte which is combined with the following 175 * byte to form a 16-bit value. Read across consecutive codeToString() calls, so kept per-thread like CONSUMER_CHARS. 176 */ 177 private static final ThreadLocal<Boolean> WIDE = ThreadLocal.withInitial(() -> Boolean.FALSE); 178 179 // A-Z, g-z, _, $ 180 private static final int FREE_CHARS = 48; 181 182 private static final int[] CHAR_MAP = new int[FREE_CHARS]; 183 184 private static final int[] MAP_CHAR = new int[256]; // Reverse map 185 186 private static final char ESCAPE_CHAR = '$'; 187 188 static { 189 Arrays.fill(MAP_CHAR, UNMAPPED); 190 int j = 0; 191 for (int i = 'A'; i <= 'Z'; i++) { 192 CHAR_MAP[j] = i; 193 MAP_CHAR[i] = j; 194 j++; 195 } 196 for (int i = 'g'; i <= 'z'; i++) { 197 CHAR_MAP[j] = i; 198 MAP_CHAR[i] = j; 199 j++; 200 } 201 CHAR_MAP[j] = '$'; 202 MAP_CHAR['$'] = j; 203 j++; 204 CHAR_MAP[j] = '_'; 205 MAP_CHAR['_'] = j; 206 } 207 208 /** 209 * Convert bit field of flags into string such as 'static final'. 210 * 211 * @param accessFlags Access flags. 212 * @return String representation of flags. 213 */ 214 public static String accessToString(final int accessFlags) { 215 return accessToString(accessFlags, false); 216 } 217 218 /** 219 * Convert bit field of flags into string such as 'static final'. 220 * <p> 221 * Special case: Classes compiled with new compilers and with the 'ACC_SUPER' flag would be said to be "synchronized". 222 * This is because SUN used the same value for the flags 'ACC_SUPER' and 'ACC_SYNCHRONIZED'. 223 * </p> 224 * 225 * @param accessFlags Access flags. 226 * @param forClass access flags are for class qualifiers ?. 227 * @return String representation of flags. 228 */ 229 public static String accessToString(final int accessFlags, final boolean forClass) { 230 final StringBuilder buf = new StringBuilder(); 231 int p = 0; 232 for (int i = 0; p < Const.MAX_ACC_FLAG_I; i++) { // Loop through known flags 233 p = pow2(i); 234 if ((accessFlags & p) != 0) { 235 /* 236 * Special case: Classes compiled with new compilers and with the 'ACC_SUPER' flag would be said to be "synchronized". 237 * This is because SUN used the same value for the flags 'ACC_SUPER' and 'ACC_SYNCHRONIZED'. 238 */ 239 if (forClass && (p == Const.ACC_SUPER || p == Const.ACC_INTERFACE)) { 240 continue; 241 } 242 buf.append(Const.getAccessName(i)).append(" "); 243 } 244 } 245 return buf.toString().trim(); 246 } 247 248 /** 249 * Convert (signed) byte to (unsigned) short value, that is, all negative values become positive. 250 */ 251 private static short byteToShort(final byte b) { 252 return b < 0 ? (short) (256 + b) : (short) b; 253 } 254 255 /** 256 * Gets the class or interface type name. 257 * 258 * @param accessFlags The class flags. 259 * @return "class" or "interface", depending on the ACC_INTERFACE flag. 260 */ 261 public static String classOrInterface(final int accessFlags) { 262 return (accessFlags & Const.ACC_INTERFACE) != 0 ? "interface" : "class"; 263 } 264 265 /** 266 * Clears a bit in a flag. 267 * 268 * @param flag The flag value. 269 * @param i The bit position. 270 * @return 'flag' with bit 'i' set to 0. 271 */ 272 public static int clearBit(final int flag, final int i) { 273 final int bit = pow2(i); 274 return (flag & bit) == 0 ? flag : flag ^ bit; 275 } 276 277 /** 278 * Disassembles byte code. 279 * 280 * @param code byte code array. 281 * @param constantPool The constant pool. 282 * @param index offset in code array. 283 * @param length number of opcodes to decompile. 284 * @return disassembled string representation. 285 */ 286 public static String codeToString(final byte[] code, final ConstantPool constantPool, final int index, final int length) { 287 return codeToString(code, constantPool, index, length, true); 288 } 289 290 /** 291 * Disassemble a byte array of JVM byte codes starting from code line 'index' and return the disassembled string 292 * representation. Decode only 'num' opcodes (including their operands), use -1 if you want to decompile everything. 293 * 294 * @param code byte code array. 295 * @param constantPool Array of constants. 296 * @param index offset in 'code' array <em>(number of opcodes, not bytes!)</em>. 297 * @param length number of opcodes to decompile, -1 for all. 298 * @param verbose be verbose, for example print constant pool index. 299 * @return String representation of byte codes. 300 */ 301 public static String codeToString(final byte[] code, final ConstantPool constantPool, final int index, final int length, final boolean verbose) { 302 final StringBuilder buf = new StringBuilder(code.length * 20); // Should be sufficient // CHECKSTYLE IGNORE MagicNumber 303 // Defend against a stale flag left behind by a previous (possibly truncated) disassembly on this thread. 304 WIDE.set(Boolean.FALSE); 305 try (ByteSequence stream = new ByteSequence(code)) { 306 for (int i = 0; i < index; i++) { 307 codeToString(stream, constantPool, verbose); 308 } 309 for (int i = 0; stream.available() > 0; i++) { 310 if (length < 0 || i < length) { 311 final String indices = fillup(stream.getIndex() + ":", 6, true, ' '); 312 buf.append(indices).append(codeToString(stream, constantPool, verbose)).append('\n'); 313 } 314 } 315 } catch (final IOException e) { 316 throw new ClassFormatException("Byte code error: " + buf.toString(), e); 317 } finally { 318 // A crafted code array can end right after a WIDE opcode (normal loop exit) or throw before the flag is 319 // consumed; never leak the flag to the next disassembly on this thread, or that (unrelated) input is 320 // mis-decoded from its first load/store/iinc/ret instruction onwards. 321 WIDE.remove(); 322 } 323 return buf.toString(); 324 } 325 326 /** 327 * Disassembles byte code. 328 * 329 * @param bytes stream of bytes. 330 * @param constantPool The constant pool. 331 * @return disassembled string representation. 332 * @throws IOException Thrown if a failure from reading from the bytes argument occurs. 333 */ 334 public static String codeToString(final ByteSequence bytes, final ConstantPool constantPool) throws IOException { 335 return codeToString(bytes, constantPool, true); 336 } 337 338 /** 339 * Disassemble a stream of byte codes and return the string representation. 340 * 341 * @param bytes stream of bytes. 342 * @param constantPool Array of constants. 343 * @param verbose be verbose, for example print constant pool index. 344 * @return String representation of byte code. 345 * @throws IOException Thrown if a failure from reading from the bytes argument occurs 346 */ 347 public static String codeToString(final ByteSequence bytes, final ConstantPool constantPool, final boolean verbose) throws IOException { 348 final short opcode = (short) bytes.readUnsignedByte(); 349 int defaultOffset = 0; 350 final int low; 351 final int high; 352 final int npairs; 353 final int index; 354 final int vindex; 355 final int constant; 356 final int[] match; 357 final int[] jumpTable; 358 int noPadBytes = 0; 359 final int offset; 360 final StringBuilder buf = new StringBuilder(Const.getOpcodeName(opcode)); 361 /* 362 * Special case: Skip (0-3) padding bytes, that is, the following bytes are 4-byte-aligned 363 */ 364 if (opcode == Const.TABLESWITCH || opcode == Const.LOOKUPSWITCH) { 365 final int remainder = bytes.getIndex() % 4; 366 noPadBytes = remainder == 0 ? 0 : 4 - remainder; 367 for (int i = 0; i < noPadBytes; i++) { 368 final byte b; 369 if ((b = bytes.readByte()) != 0) { 370 System.err.println("Warning: Padding byte != 0 in " + Const.getOpcodeName(opcode) + ":" + b); 371 } 372 } 373 // Both cases have a field default_offset in common 374 defaultOffset = bytes.readInt(); 375 } 376 switch (opcode) { 377 /* 378 * Table switch has variable length arguments. 379 */ 380 case Const.TABLESWITCH: 381 low = bytes.readInt(); 382 high = bytes.readInt(); 383 offset = bytes.getIndex() - 12 - noPadBytes - 1; 384 defaultOffset += offset; 385 // Each jump table entry is a 4 byte offset, so a well-formed table cannot declare more entries than fit 386 // into the remaining byte code; checking before allocating keeps a crafted low/high pair from forcing a 387 // huge allocation. 388 final long jumpTableLength = (long) high - low + 1; 389 if (jumpTableLength < 0 || jumpTableLength * 4 > bytes.available()) { 390 throw new ClassFormatException("Invalid TABLESWITCH: low = " + low + ", high = " + high + " but only " + bytes.available() + " bytes remain"); 391 } 392 buf.append("\tdefault = ").append(defaultOffset).append(", low = ").append(low).append(", high = ").append(high).append("("); 393 jumpTable = new int[(int) jumpTableLength]; 394 for (int i = 0; i < jumpTable.length; i++) { 395 jumpTable[i] = offset + bytes.readInt(); 396 buf.append(jumpTable[i]); 397 if (i < jumpTable.length - 1) { 398 buf.append(", "); 399 } 400 } 401 buf.append(")"); 402 break; 403 /* 404 * Lookup switch has variable length arguments. 405 */ 406 case Const.LOOKUPSWITCH: { 407 npairs = bytes.readInt(); 408 offset = bytes.getIndex() - 8 - noPadBytes - 1; 409 // Each match-offset pair is 8 bytes, see the TABLESWITCH check above. 410 if (npairs < 0 || (long) npairs * 8 > bytes.available()) { 411 throw new ClassFormatException("Invalid LOOKUPSWITCH: npairs = " + npairs + " but only " + bytes.available() + " bytes remain"); 412 } 413 match = new int[npairs]; 414 jumpTable = new int[npairs]; 415 defaultOffset += offset; 416 buf.append("\tdefault = ").append(defaultOffset).append(", npairs = ").append(npairs).append(" ("); 417 for (int i = 0; i < npairs; i++) { 418 match[i] = bytes.readInt(); 419 jumpTable[i] = offset + bytes.readInt(); 420 buf.append("(").append(match[i]).append(", ").append(jumpTable[i]).append(")"); 421 if (i < npairs - 1) { 422 buf.append(", "); 423 } 424 } 425 buf.append(")"); 426 } 427 break; 428 /* 429 * Two address bytes + offset from start of byte stream form the jump target 430 */ 431 case Const.GOTO: 432 case Const.IFEQ: 433 case Const.IFGE: 434 case Const.IFGT: 435 case Const.IFLE: 436 case Const.IFLT: 437 case Const.JSR: 438 case Const.IFNE: 439 case Const.IFNONNULL: 440 case Const.IFNULL: 441 case Const.IF_ACMPEQ: 442 case Const.IF_ACMPNE: 443 case Const.IF_ICMPEQ: 444 case Const.IF_ICMPGE: 445 case Const.IF_ICMPGT: 446 case Const.IF_ICMPLE: 447 case Const.IF_ICMPLT: 448 case Const.IF_ICMPNE: 449 buf.append("\t\t#").append(bytes.getIndex() - 1 + bytes.readShort()); 450 break; 451 /* 452 * 32-bit wide jumps 453 */ 454 case Const.GOTO_W: 455 case Const.JSR_W: 456 buf.append("\t\t#").append(bytes.getIndex() - 1 + bytes.readInt()); 457 break; 458 /* 459 * Index byte references local variable (register) 460 */ 461 case Const.ALOAD: 462 case Const.ASTORE: 463 case Const.DLOAD: 464 case Const.DSTORE: 465 case Const.FLOAD: 466 case Const.FSTORE: 467 case Const.ILOAD: 468 case Const.ISTORE: 469 case Const.LLOAD: 470 case Const.LSTORE: 471 case Const.RET: 472 if (WIDE.get().booleanValue()) { 473 vindex = bytes.readUnsignedShort(); 474 WIDE.set(Boolean.FALSE); // Clear flag 475 } else { 476 vindex = bytes.readUnsignedByte(); 477 } 478 buf.append("\t\t%").append(vindex); 479 break; 480 /* 481 * Remember wide byte which is used to form a 16-bit address in the following instruction. Relies on that the method is 482 * called again with the following opcode. 483 */ 484 case Const.WIDE: 485 WIDE.set(Boolean.TRUE); 486 buf.append("\t(wide)"); 487 break; 488 /* 489 * Array of basic type. 490 */ 491 case Const.NEWARRAY: 492 buf.append("\t\t<").append(Const.getTypeName(bytes.readByte())).append(">"); 493 break; 494 /* 495 * Access object/class fields. 496 */ 497 case Const.GETFIELD: 498 case Const.GETSTATIC: 499 case Const.PUTFIELD: 500 case Const.PUTSTATIC: 501 index = bytes.readUnsignedShort(); 502 buf.append("\t\t").append(constantPool.constantToString(index, Const.CONSTANT_Fieldref)).append(verbose ? " (" + index + ")" : ""); 503 break; 504 /* 505 * Operands are references to classes in constant pool 506 */ 507 case Const.NEW: 508 case Const.CHECKCAST: 509 buf.append("\t"); 510 index = bytes.readUnsignedShort(); 511 buf.append("\t<").append(constantPool.constantToString(index, Const.CONSTANT_Class)).append(">").append(verbose ? " (" + index + ")" : ""); 512 break; 513 case Const.INSTANCEOF: 514 index = bytes.readUnsignedShort(); 515 buf.append("\t<").append(constantPool.constantToString(index, Const.CONSTANT_Class)).append(">").append(verbose ? " (" + index + ")" : ""); 516 break; 517 /* 518 * Operands are references to methods in constant pool 519 */ 520 case Const.INVOKESPECIAL: 521 case Const.INVOKESTATIC: 522 index = bytes.readUnsignedShort(); 523 final Constant c = constantPool.getConstant(index); 524 // With Java8 operand may be either a CONSTANT_Methodref 525 // or a CONSTANT_InterfaceMethodref. (markro) 526 buf.append("\t").append(constantPool.constantToString(index, c.getTag())).append(verbose ? " (" + index + ")" : ""); 527 break; 528 case Const.INVOKEVIRTUAL: 529 index = bytes.readUnsignedShort(); 530 buf.append("\t").append(constantPool.constantToString(index, Const.CONSTANT_Methodref)).append(verbose ? " (" + index + ")" : ""); 531 break; 532 case Const.INVOKEINTERFACE: 533 index = bytes.readUnsignedShort(); 534 final int nargs = bytes.readUnsignedByte(); // historical, redundant 535 buf.append("\t").append(constantPool.constantToString(index, Const.CONSTANT_InterfaceMethodref)).append(verbose ? " (" + index + ")\t" : "") 536 .append(nargs).append("\t").append(bytes.readUnsignedByte()); // Last byte is a reserved space 537 break; 538 case Const.INVOKEDYNAMIC: 539 index = bytes.readUnsignedShort(); 540 buf.append("\t").append(constantPool.constantToString(index, Const.CONSTANT_InvokeDynamic)).append(verbose ? " (" + index + ")\t" : "") 541 .append(bytes.readUnsignedByte()) // Thrid byte is a reserved space 542 .append(bytes.readUnsignedByte()); // Last byte is a reserved space 543 break; 544 /* 545 * Operands are references to items in constant pool 546 */ 547 case Const.LDC_W: 548 case Const.LDC2_W: 549 index = bytes.readUnsignedShort(); 550 buf.append("\t\t").append(constantPool.constantToString(index, constantPool.getConstant(index).getTag())) 551 .append(verbose ? " (" + index + ")" : ""); 552 break; 553 case Const.LDC: 554 index = bytes.readUnsignedByte(); 555 buf.append("\t\t").append(constantPool.constantToString(index, constantPool.getConstant(index).getTag())) 556 .append(verbose ? " (" + index + ")" : ""); 557 break; 558 /* 559 * Array of references. 560 */ 561 case Const.ANEWARRAY: 562 index = bytes.readUnsignedShort(); 563 buf.append("\t\t<").append(compactClassName(constantPool.getConstantString(index, Const.CONSTANT_Class), false)).append(">") 564 .append(verbose ? " (" + index + ")" : ""); 565 break; 566 /* 567 * Multidimensional array of references. 568 */ 569 case Const.MULTIANEWARRAY: { 570 index = bytes.readUnsignedShort(); 571 final int dimensions = bytes.readUnsignedByte(); 572 buf.append("\t<").append(compactClassName(constantPool.getConstantString(index, Const.CONSTANT_Class), false)).append(">\t").append(dimensions) 573 .append(verbose ? " (" + index + ")" : ""); 574 } 575 break; 576 /* 577 * Increment local variable. 578 */ 579 case Const.IINC: 580 if (WIDE.get().booleanValue()) { 581 vindex = bytes.readUnsignedShort(); 582 constant = bytes.readShort(); 583 WIDE.set(Boolean.FALSE); 584 } else { 585 vindex = bytes.readUnsignedByte(); 586 constant = bytes.readByte(); 587 } 588 buf.append("\t\t%").append(vindex).append("\t").append(constant); 589 break; 590 default: 591 if (Const.getNoOfOperands(opcode) > 0) { 592 for (int i = 0; i < Const.getOperandTypeCount(opcode); i++) { 593 buf.append("\t\t"); 594 switch (Const.getOperandType(opcode, i)) { 595 case Const.T_BYTE: 596 buf.append(bytes.readByte()); 597 break; 598 case Const.T_SHORT: 599 buf.append(bytes.readShort()); 600 break; 601 case Const.T_INT: 602 buf.append(bytes.readInt()); 603 break; 604 default: // Never reached 605 throw new IllegalStateException("Unreachable default case reached."); 606 } 607 } 608 } 609 } 610 return buf.toString(); 611 } 612 613 /** 614 * Shorten long class names, <em>java/lang/String</em> becomes <em>String</em>. 615 * 616 * @param str The long class name. 617 * @return Compacted class name. 618 */ 619 public static String compactClassName(final String str) { 620 return compactClassName(str, true); 621 } 622 623 /** 624 * Shorten long class names, <em>java/lang/String</em> becomes <em>java.lang.String</em>, for example. If <em>chopit</em> is 625 * <em>true</em> the prefix <em>java.lang</em> is also removed. 626 * 627 * @param str The long class name. 628 * @param chopit flag that determines whether chopping is executed or not. 629 * @return Compacted class name. 630 */ 631 public static String compactClassName(final String str, final boolean chopit) { 632 return compactClassName(str, "java.lang.", chopit); 633 } 634 635 /** 636 * Shorten long class name <em>str</em>, that is, chop off the <em>prefix</em>, if the class name starts with this string 637 * and the flag <em>chopit</em> is true. Slashes <em>/</em> are converted to dots <em>.</em>. 638 * 639 * @param str The long class name. 640 * @param prefix The prefix the get rid off. 641 * @param chopit flag that determines whether chopping is executed or not. 642 * @return Compacted class name. 643 */ 644 public static String compactClassName(String str, final String prefix, final boolean chopit) { 645 final int len = prefix.length(); 646 str = pathToPackage(str); // Is '/' on all systems, even DOS 647 // If string starts with 'prefix' and contains no further dots 648 if (chopit && str.startsWith(prefix) && str.substring(len).indexOf('.') == -1) { 649 str = str.substring(len); 650 } 651 return str; 652 } 653 654 /** 655 * Escape all occurrences of newline chars '\n', quotes \", etc. 656 * 657 * @param label The string to convert. 658 * @return The converted string. 659 */ 660 public static String convertString(final String label) { 661 final char[] ch = label.toCharArray(); 662 final StringBuilder buf = new StringBuilder(); 663 for (final char element : ch) { 664 switch (element) { 665 case '\n': 666 buf.append("\\n"); 667 break; 668 case '\r': 669 buf.append("\\r"); 670 break; 671 case '\"': 672 buf.append("\\\""); 673 break; 674 case '\'': 675 buf.append("\\'"); 676 break; 677 case '\\': 678 buf.append("\\\\"); 679 break; 680 default: 681 buf.append(element); 682 break; 683 } 684 } 685 return buf.toString(); 686 } 687 688 private static int countBrackets(final String brackets) { 689 final char[] chars = brackets.toCharArray(); 690 int count = 0; 691 boolean open = false; 692 for (final char c : chars) { 693 switch (c) { 694 case '[': 695 if (open) { 696 throw new IllegalArgumentException("Illegally nested brackets:" + brackets); 697 } 698 open = true; 699 break; 700 case ']': 701 if (!open) { 702 throw new IllegalArgumentException("Illegally nested brackets:" + brackets); 703 } 704 open = false; 705 count++; 706 break; 707 default: 708 // Don't care 709 break; 710 } 711 } 712 if (open) { 713 throw new IllegalArgumentException("Illegally nested brackets:" + brackets); 714 } 715 return count; 716 } 717 718 /** 719 * Decode a string back to a byte array. 720 * 721 * @param s The string to convert. 722 * @param uncompress use gzip to uncompress the stream of bytes. 723 * @return The decoded byte array. 724 * @throws IOException Thrown if there's a gzip exception or the decompressed data exceeds {@code MAX_DECODED_LENGTH}. 725 */ 726 public static byte[] decode(final String s, final boolean uncompress) throws IOException { 727 final byte[] bytes; 728 try (JavaReader jr = new JavaReader(new CharArrayReader(s.toCharArray())); ByteArrayOutputStream bos = new ByteArrayOutputStream()) { 729 int ch; 730 while ((ch = jr.read()) >= 0) { 731 bos.write(ch); 732 } 733 bytes = bos.toByteArray(); 734 } 735 if (uncompress) { 736 // @formatter:off 737 try (BoundedInputStream gis = BoundedInputStream.builder() 738 .setInputStream(new GZIPInputStream(new ByteArrayInputStream(bytes))) 739 .setMaxCount(MAX_DECODED_LENGTH + 1).get()) { 740 return IOUtils.toByteArray(gis); 741 } 742 // @formatter:on 743 } 744 return bytes; 745 } 746 747 /** 748 * Encode byte array it into Java identifier string, that is, a string that only contains the following characters: (a, ... 749 * z, A, ... Z, 0, ... 9, _, $). The encoding algorithm itself is not too clever: if the current byte's ASCII value 750 * already is a valid Java identifier part, leave it as it is. Otherwise it writes the escape character($) followed by: 751 * 752 * <ul> 753 * <li>the ASCII value as a hexadecimal string, if the value is not in the range 200..247</li> 754 * <li>a Java identifier char not used in a lowercase hexadecimal string, if the value is in the range 200..247</li> 755 * </ul> 756 * 757 * <p> 758 * This operation inflates the original byte array by roughly 40-50% 759 * </p> 760 * 761 * @param bytes The byte array to convert. 762 * @param compress use gzip to minimize string. 763 * @return The encoded string. 764 * @throws IOException Thrown if there's a gzip exception. 765 */ 766 public static String encode(byte[] bytes, final boolean compress) throws IOException { 767 if (compress) { 768 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); GZIPOutputStream gos = new GZIPOutputStream(baos)) { 769 gos.write(bytes, 0, bytes.length); 770 gos.close(); 771 bytes = baos.toByteArray(); 772 } 773 } 774 final CharArrayWriter caw = new CharArrayWriter(); 775 try (JavaWriter jw = new JavaWriter(caw)) { 776 for (final byte b : bytes) { 777 final int in = b & 0x000000ff; // Normalize to unsigned 778 jw.write(in); 779 } 780 } 781 return caw.toString(); 782 } 783 784 /** 785 * Fillup char with up to length characters with char 'fill' and justify it left or right. 786 * 787 * @param str string to format. 788 * @param length length of desired string. 789 * @param leftJustify format left or right. 790 * @param fill fill character. 791 * @return formatted string. 792 */ 793 public static String fillup(final String str, final int length, final boolean leftJustify, final char fill) { 794 final int len = length - str.length(); 795 final char[] buf = ArrayFill.fill(new char[Math.max(len, 0)], fill); 796 if (leftJustify) { 797 return str + new String(buf); 798 } 799 return new String(buf) + str; 800 } 801 802 /** 803 * Return a string for an integer justified left or right and filled up with 'fill' characters if necessary. 804 * 805 * @param i integer to format. 806 * @param length length of desired string. 807 * @param leftJustify format left or right. 808 * @param fill fill character. 809 * @return formatted int. 810 */ 811 public static String format(final int i, final int length, final boolean leftJustify, final char fill) { 812 return fillup(Integer.toString(i), length, leftJustify, fill); 813 } 814 815 /** 816 * Parse Java type such as "char", or "java.lang.String[]" and return the signature in byte code format, for example "C" or 817 * "[Ljava/lang/String;" respectively. 818 * 819 * @param type Java type. 820 * @return byte code signature. 821 */ 822 public static String getSignature(String type) { 823 final StringBuilder buf = new StringBuilder(); 824 final char[] chars = type.toCharArray(); 825 boolean charFound = false; 826 boolean delim = false; 827 int index = -1; 828 loop: for (int i = 0; i < chars.length; i++) { 829 switch (chars[i]) { 830 case ' ': 831 case '\t': 832 case '\n': 833 case '\r': 834 case '\f': 835 if (charFound) { 836 delim = true; 837 } 838 break; 839 case '[': 840 if (!charFound) { 841 throw new IllegalArgumentException("Illegal type: " + type); 842 } 843 index = i; 844 break loop; 845 default: 846 charFound = true; 847 if (!delim) { 848 buf.append(chars[i]); 849 } 850 } 851 } 852 int brackets = 0; 853 if (index > 0) { 854 brackets = countBrackets(type.substring(index)); 855 } 856 type = buf.toString(); 857 buf.setLength(0); 858 for (int i = 0; i < brackets; i++) { 859 buf.append('['); 860 } 861 boolean found = false; 862 for (int i = Const.T_BOOLEAN; i <= Const.T_VOID && !found; i++) { 863 if (Const.getTypeName(i).equals(type)) { 864 found = true; 865 buf.append(Const.getShortTypeName(i)); 866 } 867 } 868 if (!found) { 869 buf.append('L').append(packageToPath(type)).append(';'); 870 } 871 return buf.toString(); 872 } 873 874 private static boolean isHex(final int i) { 875 return i >= '0' && i <= '9' || i >= 'a' && i <= 'f'; 876 } 877 878 /** 879 * WARNING: 880 * 881 * There is some nomenclature confusion through much of the BCEL code base with respect to the terms Descriptor and 882 * Signature. For the offical definitions see: 883 * 884 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3"> Descriptors in The Java 885 * Virtual Machine Specification</a> 886 * 887 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.9.1"> Signatures in The Java 888 * Virtual Machine Specification</a> 889 * 890 * In brief, a descriptor is a string representing the type of a field or method. Signatures are similar, but more 891 * complex. Signatures are used to encode declarations written in the Java programming language that use types 892 * outside the type system of the Java Virtual Machine. They are used to describe the type of any class, interface, 893 * constructor, method or field whose declaration uses type variables or parameterized types. 894 * 895 * To parse a descriptor, call typeSignatureToString. To parse a signature, call signatureToString. 896 * 897 * Note that if the signature string is a single, non-generic item, the call to signatureToString reduces to a call 898 * to typeSignatureToString. Also note, that if you only wish to parse the first item in a longer signature string, 899 * you should call typeSignatureToString directly. 900 */ 901 902 /** 903 * Tests if a character is part of a Java identifier. 904 * 905 * @param ch The character to test if it's part of an identifier. 906 * @return true, if character is one of (a, ... z, A, ... Z, 0, ... 9, _). 907 */ 908 public static boolean isJavaIdentifierPart(final char ch) { 909 return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch == '_'; 910 } 911 912 /** 913 * Tests if a bit is set. 914 * 915 * @param flag The flag value. 916 * @param i The bit position. 917 * @return true, if bit 'i' in 'flag' is set. 918 */ 919 public static boolean isSet(final int flag, final int i) { 920 return (flag & pow2(i)) != 0; 921 } 922 923 /** 924 * Converts argument list portion of method signature to string with all class names compacted. 925 * 926 * @param signature Method signature. 927 * @return String Array of argument types. 928 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file 929 */ 930 public static String[] methodSignatureArgumentTypes(final String signature) throws ClassFormatException { 931 return methodSignatureArgumentTypes(signature, true); 932 } 933 934 /** 935 * Converts argument list portion of method signature to string. 936 * 937 * @param signature Method signature. 938 * @param chopit flag that determines whether chopping is executed or not. 939 * @return String Array of argument types. 940 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file 941 */ 942 public static String[] methodSignatureArgumentTypes(final String signature, final boolean chopit) throws ClassFormatException { 943 final List<String> vec = new ArrayList<>(); 944 int index; 945 try { 946 // Skip any type arguments to read argument declarations between '(' and ')' 947 index = signature.indexOf('(') + 1; 948 if (index <= 0) { 949 throw new InvalidMethodSignatureException(signature); 950 } 951 while (signature.charAt(index) != ')') { 952 vec.add(typeSignatureToString(signature.substring(index), chopit)); 953 // corrected concurrent private static field acess 954 index += unwrap(CONSUMER_CHARS); // update position 955 } 956 } catch (final StringIndexOutOfBoundsException e) { // Should never occur 957 throw new InvalidMethodSignatureException(signature, e); 958 } 959 return vec.toArray(ArrayUtils.EMPTY_STRING_ARRAY); 960 } 961 962 /** 963 * Converts return type portion of method signature to string with all class names compacted. 964 * 965 * @param signature Method signature. 966 * @return String representation of method return type. 967 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file 968 */ 969 public static String methodSignatureReturnType(final String signature) throws ClassFormatException { 970 return methodSignatureReturnType(signature, true); 971 } 972 973 /** 974 * Converts return type portion of method signature to string. 975 * 976 * @param signature Method signature. 977 * @param chopit flag that determines whether chopping is executed or not. 978 * @return String representation of method return type. 979 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file 980 */ 981 public static String methodSignatureReturnType(final String signature, final boolean chopit) throws ClassFormatException { 982 try { 983 // Read return type after ')' 984 final int index = signature.lastIndexOf(')') + 1; 985 if (index <= 0) { 986 throw new InvalidMethodSignatureException(signature); 987 } 988 return typeSignatureToString(signature.substring(index), chopit); 989 } catch (final StringIndexOutOfBoundsException e) { // Should never occur 990 throw new InvalidMethodSignatureException(signature, e); 991 } 992 } 993 994 /** 995 * Converts method signature to string with all class names compacted. 996 * 997 * @param signature to convert. 998 * @param name of method. 999 * @param access flags of method. 1000 * @return Human readable signature. 1001 */ 1002 public static String methodSignatureToString(final String signature, final String name, final String access) { 1003 return methodSignatureToString(signature, name, access, true); 1004 } 1005 1006 /** 1007 * Converts method signature to string. 1008 * 1009 * @param signature to convert. 1010 * @param name of method. 1011 * @param access flags of method. 1012 * @param chopit flag that determines whether chopping is executed or not. 1013 * @return Human readable signature. 1014 */ 1015 public static String methodSignatureToString(final String signature, final String name, final String access, final boolean chopit) { 1016 return methodSignatureToString(signature, name, access, chopit, null); 1017 } 1018 1019 /** 1020 * This method converts a method signature string into a Java type declaration like 'void main(String[])' and throws a 1021 * 'ClassFormatException' when the parsed type is invalid. 1022 * 1023 * @param signature Method signature. 1024 * @param name Method name. 1025 * @param access Method access rights. 1026 * @param chopit flag that determines whether chopping is executed or not. 1027 * @param vars The LocalVariableTable for the method. 1028 * @return Java type declaration. 1029 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file 1030 */ 1031 public static String methodSignatureToString(final String signature, final String name, final String access, final boolean chopit, 1032 final LocalVariableTable vars) throws ClassFormatException { 1033 final StringBuilder buf = new StringBuilder("("); 1034 final String type; 1035 int index; 1036 int varIndex = access.contains("static") ? 0 : 1; 1037 try { 1038 // Skip any type arguments to read argument declarations between '(' and ')' 1039 index = signature.indexOf('(') + 1; 1040 if (index <= 0) { 1041 throw new InvalidMethodSignatureException(signature); 1042 } 1043 while (signature.charAt(index) != ')') { 1044 final String paramType = typeSignatureToString(signature.substring(index), chopit); 1045 buf.append(paramType); 1046 if (vars != null) { 1047 final LocalVariable l = vars.getLocalVariable(varIndex, 0); 1048 if (l != null) { 1049 buf.append(" ").append(l.getName()); 1050 } 1051 } else { 1052 buf.append(" arg").append(varIndex); 1053 } 1054 if ("double".equals(paramType) || "long".equals(paramType)) { 1055 varIndex += 2; 1056 } else { 1057 varIndex++; 1058 } 1059 buf.append(", "); 1060 // corrected concurrent private static field acess 1061 index += unwrap(CONSUMER_CHARS); // update position 1062 } 1063 index++; // update position 1064 // Read return type after ')' 1065 type = typeSignatureToString(signature.substring(index), chopit); 1066 } catch (final StringIndexOutOfBoundsException e) { // Should never occur 1067 throw new InvalidMethodSignatureException(signature, e); 1068 } 1069 // ignore any throws information in the signature 1070 if (buf.length() > 1) { 1071 buf.setLength(buf.length() - 2); 1072 } 1073 buf.append(")"); 1074 return access + (!access.isEmpty() ? " " : "") + // May be an empty string 1075 type + " " + name + buf.toString(); 1076 } 1077 1078 /** 1079 * Converts string containing the method return and argument types to a byte code method signature. 1080 * 1081 * @param ret Return type of method. 1082 * @param argv Types of method arguments. 1083 * @return Byte code representation of method signature. 1084 * @throws ClassFormatException Thrown if the signature is for Void 1085 */ 1086 public static String methodTypeToSignature(final String ret, final String[] argv) throws ClassFormatException { 1087 final StringBuilder buf = new StringBuilder("("); 1088 String str; 1089 if (argv != null) { 1090 for (final String element : argv) { 1091 str = getSignature(element); 1092 if (str.endsWith("V")) { 1093 throw new ClassFormatException("Invalid type: " + element); 1094 } 1095 buf.append(str); 1096 } 1097 } 1098 str = getSignature(ret); 1099 buf.append(")").append(str); 1100 return buf.toString(); 1101 } 1102 1103 /** 1104 * Converts '.'s to '/'s. 1105 * 1106 * @param name Source. 1107 * @return converted value. 1108 * @since 6.7.0 1109 */ 1110 public static String packageToPath(final String name) { 1111 return name.replace('.', '/'); 1112 } 1113 1114 /** 1115 * Converts a path to a package name. 1116 * 1117 * @param str The source path. 1118 * @return A package name. 1119 * @since 6.6.0 1120 */ 1121 public static String pathToPackage(final String str) { 1122 return str.replace('/', '.'); 1123 } 1124 1125 private static int pow2(final int n) { 1126 return 1 << n; 1127 } 1128 1129 /** 1130 * Prints an array to a string. 1131 * 1132 * @param obj The array to print. 1133 * @return The string representation. 1134 */ 1135 public static String printArray(final Object[] obj) { 1136 return printArray(obj, true); 1137 } 1138 1139 /** 1140 * Prints an array to a string. 1141 * 1142 * @param obj The array to print. 1143 * @param braces whether to include braces. 1144 * @return The string representation. 1145 */ 1146 public static String printArray(final Object[] obj, final boolean braces) { 1147 return printArray(obj, braces, false); 1148 } 1149 1150 /** 1151 * Prints an array to a string. 1152 * 1153 * @param obj The array to print. 1154 * @param braces whether to include braces. 1155 * @param quote whether to quote elements. 1156 * @return The string representation. 1157 */ 1158 public static String printArray(final Object[] obj, final boolean braces, final boolean quote) { 1159 if (obj == null) { 1160 return null; 1161 } 1162 final StringBuilder buf = new StringBuilder(); 1163 if (braces) { 1164 buf.append('{'); 1165 } 1166 for (int i = 0; i < obj.length; i++) { 1167 if (obj[i] != null) { 1168 buf.append(quote ? "\"" : "").append(obj[i]).append(quote ? "\"" : ""); 1169 } else { 1170 buf.append("null"); 1171 } 1172 if (i < obj.length - 1) { 1173 buf.append(", "); 1174 } 1175 } 1176 if (braces) { 1177 buf.append('}'); 1178 } 1179 return buf.toString(); 1180 } 1181 1182 /** 1183 * Prints an array to a stream. 1184 * 1185 * @param out The output stream. 1186 * @param obj The array to print. 1187 */ 1188 public static void printArray(final PrintStream out, final Object[] obj) { 1189 out.println(printArray(obj, true)); 1190 } 1191 1192 /** 1193 * Prints an array to a writer. 1194 * 1195 * @param out The output writer. 1196 * @param obj The array to print. 1197 */ 1198 public static void printArray(final PrintWriter out, final Object[] obj) { 1199 out.println(printArray(obj, true)); 1200 } 1201 1202 /** 1203 * Replace all occurrences of <em>old</em> in <em>str</em> with <em>new</em>. 1204 * 1205 * @param str String to permute. 1206 * @param old String to be replaced. 1207 * @param new_ Replacement string. 1208 * @return new String object. 1209 */ 1210 public static String replace(String str, final String old, final String new_) { 1211 int index; 1212 int oldIndex; 1213 try { 1214 if (str.contains(old)) { // 'old' found in str 1215 final StringBuilder buf = new StringBuilder(); 1216 oldIndex = 0; // String start offset 1217 // While we have something to replace 1218 while ((index = str.indexOf(old, oldIndex)) != -1) { 1219 buf.append(str, oldIndex, index); // append prefix 1220 buf.append(new_); // append replacement 1221 oldIndex = index + old.length(); // Skip 'old'.length chars 1222 } 1223 buf.append(str.substring(oldIndex)); // append rest of string 1224 str = buf.toString(); 1225 } 1226 } catch (final StringIndexOutOfBoundsException e) { // Should not occur 1227 System.err.println(e); 1228 } 1229 return str; 1230 } 1231 1232 /** 1233 * Map opcode names to opcode numbers. E.g., return Constants.ALOAD for "aload". 1234 * 1235 * @param name The opcode name. 1236 * @return The value. 1237 */ 1238 public static short searchOpcode(final String name) { 1239 final String lcName = StringUtils.toRootLowerCase(name); 1240 for (short i = 0; i < Const.OPCODE_NAMES_LENGTH; i++) { 1241 if (Const.getOpcodeName(i).equals(lcName)) { 1242 return i; 1243 } 1244 } 1245 return -1; 1246 } 1247 1248 /** 1249 * Sets a bit in a flag. 1250 * 1251 * @param flag The flag value. 1252 * @param i The bit position. 1253 * @return 'flag' with bit 'i' set to 1. 1254 */ 1255 public static int setBit(final int flag, final int i) { 1256 return flag | pow2(i); 1257 } 1258 1259 /** 1260 * Converts a signature to a string with all class names compacted. Class, Method and Type signatures are supported. 1261 * Enum and Interface signatures are not supported. 1262 * 1263 * @param signature signature to convert. 1264 * @return String containg human readable signature. 1265 */ 1266 public static String signatureToString(final String signature) { 1267 return signatureToString(signature, true); 1268 } 1269 1270 /** 1271 * Converts a signature to a string. Class, Method and Type signatures are supported. Enum and Interface signatures are 1272 * not supported. 1273 * 1274 * @param signature signature to convert. 1275 * @param chopit flag that determines whether chopping is executed or not. 1276 * @return String containg human readable signature. 1277 */ 1278 public static String signatureToString(final String signature, final boolean chopit) { 1279 String type = ""; 1280 String typeParams = ""; 1281 int index = 0; 1282 if (signature.charAt(0) == '<') { 1283 // we have type parameters 1284 typeParams = typeParamTypesToString(signature, chopit); 1285 index += unwrap(CONSUMER_CHARS); // update position 1286 } 1287 if (signature.charAt(index) == '(') { 1288 // We have a Method signature. 1289 // add types of arguments 1290 type = typeParams + typeSignaturesToString(signature.substring(index), chopit, ')'); 1291 index += unwrap(CONSUMER_CHARS); // update position 1292 // add return type 1293 type += typeSignatureToString(signature.substring(index), chopit); 1294 index += unwrap(CONSUMER_CHARS); // update position 1295 // ignore any throws information in the signature 1296 return type; 1297 } 1298 // Could be Class or Type... 1299 type = typeSignatureToString(signature.substring(index), chopit); 1300 index += unwrap(CONSUMER_CHARS); // update position 1301 if (typeParams.isEmpty() && index == signature.length()) { 1302 // We have a Type signature. 1303 return type; 1304 } 1305 // We have a Class signature. 1306 final StringBuilder typeClass = new StringBuilder(typeParams); 1307 typeClass.append(" extends "); 1308 typeClass.append(type); 1309 if (index < signature.length()) { 1310 typeClass.append(" implements "); 1311 typeClass.append(typeSignatureToString(signature.substring(index), chopit)); 1312 index += unwrap(CONSUMER_CHARS); // update position 1313 } 1314 while (index < signature.length()) { 1315 typeClass.append(", "); 1316 typeClass.append(typeSignatureToString(signature.substring(index), chopit)); 1317 index += unwrap(CONSUMER_CHARS); // update position 1318 } 1319 return typeClass.toString(); 1320 } 1321 1322 /** 1323 * Convert bytes into hexadecimal string 1324 * 1325 * @param bytes An array of bytes to convert to hexadecimal. 1326 * @return bytes as hexadecimal string, for example 00 fa 12 ... 1327 */ 1328 public static String toHexString(final byte[] bytes) { 1329 final StringBuilder buf = new StringBuilder(); 1330 for (int i = 0; i < bytes.length; i++) { 1331 final short b = byteToShort(bytes[i]); 1332 final String hex = Integer.toHexString(b); 1333 if (b < 0x10) { 1334 buf.append('0'); 1335 } 1336 buf.append(hex); 1337 if (i < bytes.length - 1) { 1338 buf.append(' '); 1339 } 1340 } 1341 return buf.toString(); 1342 } 1343 1344 /** 1345 * Return type of method signature as a byte value as defined in <em>Constants</em> 1346 * 1347 * @param signature in format described above. 1348 * @return type of method signature. 1349 * @see Const 1350 * @throws ClassFormatException Thrown if signature is not a method signature 1351 */ 1352 public static byte typeOfMethodSignature(final String signature) throws ClassFormatException { 1353 try { 1354 if (signature.charAt(0) != '(') { 1355 throw new InvalidMethodSignatureException(signature); 1356 } 1357 final int index = signature.lastIndexOf(')') + 1; 1358 return typeOfSignature(signature.substring(index)); 1359 } catch (final StringIndexOutOfBoundsException e) { 1360 throw new InvalidMethodSignatureException(signature, e); 1361 } 1362 } 1363 1364 /** 1365 * Return type of signature as a byte value as defined in <em>Constants</em> 1366 * 1367 * @param signature in format described above. 1368 * @return type of signature. 1369 * @see Const 1370 * @throws ClassFormatException Thrown if signature isn't a known type 1371 */ 1372 public static byte typeOfSignature(final String signature) throws ClassFormatException { 1373 try { 1374 switch (signature.charAt(0)) { 1375 case 'B': 1376 return Const.T_BYTE; 1377 case 'C': 1378 return Const.T_CHAR; 1379 case 'D': 1380 return Const.T_DOUBLE; 1381 case 'F': 1382 return Const.T_FLOAT; 1383 case 'I': 1384 return Const.T_INT; 1385 case 'J': 1386 return Const.T_LONG; 1387 case 'L': 1388 case 'T': 1389 return Const.T_REFERENCE; 1390 case '[': 1391 return Const.T_ARRAY; 1392 case 'V': 1393 return Const.T_VOID; 1394 case 'Z': 1395 return Const.T_BOOLEAN; 1396 case 'S': 1397 return Const.T_SHORT; 1398 case '!': 1399 case '+': 1400 case '*': 1401 return typeOfSignature(signature.substring(1)); 1402 default: 1403 throw new InvalidMethodSignatureException(signature); 1404 } 1405 } catch (final StringIndexOutOfBoundsException e) { 1406 throw new InvalidMethodSignatureException(signature, e); 1407 } 1408 } 1409 1410 /** 1411 * Converts a type parameter list signature to a string. 1412 * 1413 * @param signature signature to convert. 1414 * @param chopit flag that determines whether chopping is executed or not. 1415 * @return String containg human readable signature. 1416 */ 1417 private static String typeParamTypesToString(final String signature, final boolean chopit) { 1418 // The first character is guranteed to be '<' 1419 final StringBuilder typeParams = new StringBuilder("<"); 1420 int index = 1; // skip the '<' 1421 // get the first TypeParameter 1422 typeParams.append(typeParamTypeToString(signature.substring(index), chopit)); 1423 index += unwrap(CONSUMER_CHARS); // update position 1424 // are there more TypeParameters? 1425 while (signature.charAt(index) != '>') { 1426 typeParams.append(", "); 1427 typeParams.append(typeParamTypeToString(signature.substring(index), chopit)); 1428 index += unwrap(CONSUMER_CHARS); // update position 1429 } 1430 wrap(CONSUMER_CHARS, index + 1); // account for the '>' char 1431 return typeParams.append(">").toString(); 1432 } 1433 1434 /** 1435 * Converts a type parameter signature to a string. 1436 * 1437 * @param signature signature to convert. 1438 * @param chopit flag that determines whether chopping is executed or not. 1439 * @return String containg human readable signature. 1440 */ 1441 private static String typeParamTypeToString(final String signature, final boolean chopit) { 1442 int index = signature.indexOf(':'); 1443 if (index <= 0) { 1444 throw new ClassFormatException("Invalid type parameter signature: " + signature); 1445 } 1446 // get the TypeParameter identifier 1447 final StringBuilder typeParam = new StringBuilder(signature.substring(0, index)); 1448 index++; // account for the ':' 1449 if (signature.charAt(index) != ':') { 1450 // we have a class bound 1451 typeParam.append(" extends "); 1452 typeParam.append(typeSignatureToString(signature.substring(index), chopit)); 1453 index += unwrap(CONSUMER_CHARS); // update position 1454 } 1455 // look for interface bounds 1456 while (signature.charAt(index) == ':') { 1457 index++; // skip over the ':' 1458 typeParam.append(" & "); 1459 typeParam.append(typeSignatureToString(signature.substring(index), chopit)); 1460 index += unwrap(CONSUMER_CHARS); // update position 1461 } 1462 wrap(CONSUMER_CHARS, index); 1463 return typeParam.toString(); 1464 } 1465 1466 /** 1467 * Converts a list of type signatures to a string. 1468 * 1469 * @param signature signature to convert. 1470 * @param chopit flag that determines whether chopping is executed or not. 1471 * @param term character indicating the end of the list. 1472 * @return String containg human readable signature. 1473 */ 1474 private static String typeSignaturesToString(final String signature, final boolean chopit, final char term) { 1475 // The first character will be an 'open' that matches the 'close' contained in term. 1476 final StringBuilder typeList = new StringBuilder(signature.substring(0, 1)); 1477 int index = 1; // skip the 'open' character 1478 // get the first Type in the list 1479 if (signature.charAt(index) != term) { 1480 typeList.append(typeSignatureToString(signature.substring(index), chopit)); 1481 index += unwrap(CONSUMER_CHARS); // update position 1482 } 1483 // are there more types in the list? 1484 while (signature.charAt(index) != term) { 1485 typeList.append(", "); 1486 typeList.append(typeSignatureToString(signature.substring(index), chopit)); 1487 index += unwrap(CONSUMER_CHARS); // update position 1488 } 1489 wrap(CONSUMER_CHARS, index + 1); // account for the term char 1490 return typeList.append(term).toString(); 1491 } 1492 1493 /** 1494 * 1495 * This method converts a type signature string into a Java type declaration such as 'String[]' and throws a 1496 * 'ClassFormatException' when the parsed type is invalid. 1497 * 1498 * @param signature type signature. 1499 * @param chopit flag that determines whether chopping is executed or not. 1500 * @return string containing human readable type signature. 1501 * @throws ClassFormatException Thrown if a class is malformed or cannot be interpreted as a class file 1502 * @since 6.4.0 1503 */ 1504 public static String typeSignatureToString(final String signature, final boolean chopit) throws ClassFormatException { 1505 return typeSignatureToString(signature, chopit, 0); 1506 } 1507 1508 /** 1509 * Recursive worker for {@link #typeSignatureToString(String, boolean)} carrying the current nesting depth. 1510 * 1511 * @param signature type signature. 1512 * @param chopit flag that determines whether chopping is executed or not. 1513 * @param depth current nesting depth. 1514 * @return string containing human readable type signature. 1515 * @throws ClassFormatException Thrown if the signature is malformed or nested deeper than {@code MAX_SIGNATURE_NESTING}. 1516 */ 1517 private static String typeSignatureToString(final String signature, final boolean chopit, final int depth) throws ClassFormatException { 1518 if (depth > MAX_SIGNATURE_NESTING) { 1519 throw new ClassFormatException("Invalid signature: nesting depth exceeds " + MAX_SIGNATURE_NESTING); 1520 } 1521 // corrected concurrent private static field acess 1522 wrap(CONSUMER_CHARS, 1); // This is the default, read just one char like 'B' 1523 try { 1524 switch (signature.charAt(0)) { 1525 case 'B': 1526 return "byte"; 1527 case 'C': 1528 return "char"; 1529 case 'D': 1530 return "double"; 1531 case 'F': 1532 return "float"; 1533 case 'I': 1534 return "int"; 1535 case 'J': 1536 return "long"; 1537 case 'T': { // TypeVariableSignature 1538 final int index = signature.indexOf(';'); // Look for closing ';' 1539 if (index < 0) { 1540 throw new ClassFormatException("Invalid type variable signature: " + signature); 1541 } 1542 // corrected concurrent private static field acess 1543 wrap(CONSUMER_CHARS, index + 1); // "Tblabla;" 'T' and ';' are removed 1544 return compactClassName(signature.substring(1, index), chopit); 1545 } 1546 case 'L': { // Full class name 1547 // should this be a while loop? can there be more than 1548 // one generic clause? (markro) 1549 int fromIndex = signature.indexOf('<'); // generic type? 1550 if (fromIndex < 0) { 1551 fromIndex = 0; 1552 } else { 1553 fromIndex = signature.indexOf('>', fromIndex); 1554 if (fromIndex < 0) { 1555 throw new ClassFormatException("Invalid signature: " + signature); 1556 } 1557 } 1558 final int index = signature.indexOf(';', fromIndex); // Look for closing ';' 1559 if (index < 0) { 1560 throw new ClassFormatException("Invalid signature: " + signature); 1561 } 1562 1563 // check to see if there are any TypeArguments 1564 final int bracketIndex = signature.substring(0, index).indexOf('<'); 1565 if (bracketIndex < 0) { 1566 // just a class identifier 1567 wrap(CONSUMER_CHARS, index + 1); // "Lblabla;" 'L' and ';' are removed 1568 return compactClassName(signature.substring(1, index), chopit); 1569 } 1570 // but make sure we are not looking past the end of the current item 1571 fromIndex = signature.indexOf(';'); 1572 if (fromIndex < 0) { 1573 throw new ClassFormatException("Invalid signature: " + signature); 1574 } 1575 if (fromIndex < bracketIndex) { 1576 // just a class identifier 1577 wrap(CONSUMER_CHARS, fromIndex + 1); // "Lblabla;" 'L' and ';' are removed 1578 return compactClassName(signature.substring(1, fromIndex), chopit); 1579 } 1580 1581 // we have TypeArguments; build up partial result 1582 // as we recurse for each TypeArgument 1583 final StringBuilder type = new StringBuilder(compactClassName(signature.substring(1, bracketIndex), chopit)).append("<"); 1584 int consumedChars = bracketIndex + 1; // Shadows global var 1585 1586 // check for wildcards 1587 if (signature.charAt(consumedChars) == '+') { 1588 type.append("? extends "); 1589 consumedChars++; 1590 } else if (signature.charAt(consumedChars) == '-') { 1591 type.append("? super "); 1592 consumedChars++; 1593 } 1594 1595 // get the first TypeArgument 1596 if (signature.charAt(consumedChars) == '*') { 1597 type.append("?"); 1598 consumedChars++; 1599 } else { 1600 type.append(typeSignatureToString(signature.substring(consumedChars), chopit, depth + 1)); 1601 // update our consumed count by the number of characters the for type argument 1602 consumedChars = unwrap(CONSUMER_CHARS) + consumedChars; 1603 wrap(CONSUMER_CHARS, consumedChars); 1604 } 1605 1606 // are there more TypeArguments? 1607 while (signature.charAt(consumedChars) != '>') { 1608 type.append(", "); 1609 // check for wildcards 1610 if (signature.charAt(consumedChars) == '+') { 1611 type.append("? extends "); 1612 consumedChars++; 1613 } else if (signature.charAt(consumedChars) == '-') { 1614 type.append("? super "); 1615 consumedChars++; 1616 } 1617 if (signature.charAt(consumedChars) == '*') { 1618 type.append("?"); 1619 consumedChars++; 1620 } else { 1621 type.append(typeSignatureToString(signature.substring(consumedChars), chopit, depth + 1)); 1622 // update our consumed count by the number of characters the for type argument 1623 consumedChars = unwrap(CONSUMER_CHARS) + consumedChars; 1624 wrap(CONSUMER_CHARS, consumedChars); 1625 } 1626 } 1627 1628 // process the closing ">" 1629 consumedChars++; 1630 type.append(">"); 1631 1632 if (signature.charAt(consumedChars) == '.') { 1633 // we have a ClassTypeSignatureSuffix 1634 type.append("."); 1635 // convert SimpleClassTypeSignature to fake ClassTypeSignature 1636 // and then recurse to parse it 1637 type.append(typeSignatureToString("L" + signature.substring(consumedChars + 1), chopit, depth + 1)); 1638 // update our consumed count by the number of characters the for type argument 1639 // note that this count includes the "L" we added, but that is ok 1640 // as it accounts for the "." we didn't consume 1641 consumedChars = unwrap(CONSUMER_CHARS) + consumedChars; 1642 wrap(CONSUMER_CHARS, consumedChars); 1643 return type.toString(); 1644 } 1645 if (signature.charAt(consumedChars) != ';') { 1646 throw new ClassFormatException("Invalid signature: " + signature); 1647 } 1648 wrap(CONSUMER_CHARS, consumedChars + 1); // remove final ";" 1649 return type.toString(); 1650 } 1651 case 'S': 1652 return "short"; 1653 case 'Z': 1654 return "boolean"; 1655 case '[': { // Array declaration 1656 int n; 1657 final StringBuilder brackets = new StringBuilder(); // Accumulate []'s 1658 // Count opening brackets and look for optional size argument 1659 for (n = 0; signature.charAt(n) == '['; n++) { 1660 brackets.append("[]"); 1661 } 1662 final int consumedChars = n; // Remember value 1663 // The rest of the string denotes a '<field_type>' 1664 final String type = typeSignatureToString(signature.substring(n), chopit, depth + 1); 1665 // corrected concurrent private static field acess 1666 // consumed_chars += consumed_chars; is replaced by: 1667 final int temp = unwrap(CONSUMER_CHARS) + consumedChars; 1668 wrap(CONSUMER_CHARS, temp); 1669 return type + brackets.toString(); 1670 } 1671 case 'V': 1672 return "void"; 1673 default: 1674 throw new ClassFormatException("Invalid signature: '" + signature + "'"); 1675 } 1676 } catch (final StringIndexOutOfBoundsException e) { // Should never occur 1677 throw new ClassFormatException("Invalid signature: " + signature, e); 1678 } 1679 } 1680 1681 private static int unwrap(final ThreadLocal<Integer> tl) { 1682 return tl.get().intValue(); 1683 } 1684 1685 private static void wrap(final ThreadLocal<Integer> tl, final int value) { 1686 tl.set(Integer.valueOf(value)); 1687 } 1688 1689 /** 1690 * Constructs a Utility. 1691 * 1692 * @deprecated Will be private in the next major release. 1693 */ 1694 public Utility() { 1695 // Default constructor for subclasses 1696 } 1697 1698}