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.generic; 020 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.Collections; 024import java.util.Comparator; 025import java.util.Hashtable; 026import java.util.List; 027import java.util.Objects; 028import java.util.Stack; 029import java.util.stream.Collectors; 030 031import org.apache.bcel.Const; 032import org.apache.bcel.classfile.AnnotationEntry; 033import org.apache.bcel.classfile.Annotations; 034import org.apache.bcel.classfile.Attribute; 035import org.apache.bcel.classfile.Code; 036import org.apache.bcel.classfile.CodeException; 037import org.apache.bcel.classfile.ExceptionTable; 038import org.apache.bcel.classfile.LineNumber; 039import org.apache.bcel.classfile.LineNumberTable; 040import org.apache.bcel.classfile.LocalVariable; 041import org.apache.bcel.classfile.LocalVariableTable; 042import org.apache.bcel.classfile.LocalVariableTypeTable; 043import org.apache.bcel.classfile.Method; 044import org.apache.bcel.classfile.ParameterAnnotationEntry; 045import org.apache.bcel.classfile.ParameterAnnotations; 046import org.apache.bcel.classfile.RuntimeVisibleParameterAnnotations; 047import org.apache.bcel.classfile.Utility; 048import org.apache.bcel.util.Args; 049import org.apache.bcel.util.BCELComparator; 050import org.apache.commons.lang3.ArrayUtils; 051import org.apache.commons.lang3.stream.Streams; 052 053/** 054 * Template class for building up a method. This is done by defining exception handlers, adding thrown exceptions, local 055 * variables and attributes, whereas the 'LocalVariableTable' and 'LineNumberTable' attributes will be set automatically 056 * for the code. Use stripAttributes() if you don't like this. 057 * 058 * While generating code it may be necessary to insert NOP operations. You can use the 'removeNOPs' method to get rid 059 * off them. The resulting method object can be obtained via the 'getMethod()' method. 060 * 061 * @see InstructionList 062 * @see Method 063 */ 064public class MethodGen extends FieldGenOrMethodGen { 065 066 static final class BranchStack { 067 068 private final Stack<BranchTarget> branchTargets = new Stack<>(); 069 private final Hashtable<InstructionHandle, BranchTarget> visitedTargets = new Hashtable<>(); 070 071 public BranchTarget pop() { 072 if (!branchTargets.empty()) { 073 return branchTargets.pop(); 074 } 075 return null; 076 } 077 078 public void push(final InstructionHandle target, final int stackDepth) { 079 if (visited(target)) { 080 return; 081 } 082 branchTargets.push(visit(target, stackDepth)); 083 } 084 085 private BranchTarget visit(final InstructionHandle target, final int stackDepth) { 086 final BranchTarget bt = new BranchTarget(target, stackDepth); 087 visitedTargets.put(target, bt); 088 return bt; 089 } 090 091 private boolean visited(final InstructionHandle target) { 092 return visitedTargets.get(target) != null; 093 } 094 } 095 096 static final class BranchTarget { 097 098 final InstructionHandle target; 099 final int stackDepth; 100 101 BranchTarget(final InstructionHandle target, final int stackDepth) { 102 this.target = target; 103 this.stackDepth = stackDepth; 104 } 105 } 106 107 private static BCELComparator<FieldGenOrMethodGen> bcelComparator = new BCELComparator<FieldGenOrMethodGen>() { 108 109 @Override 110 public boolean equals(final FieldGenOrMethodGen a, final FieldGenOrMethodGen b) { 111 return a == b || a != null && b != null && Objects.equals(a.getName(), b.getName()) && Objects.equals(a.getSignature(), b.getSignature()); 112 } 113 114 @Override 115 public int hashCode(final FieldGenOrMethodGen o) { 116 return o != null ? Objects.hash(o.getSignature(), o.getName()) : 0; 117 } 118 }; 119 120 private static byte[] getByteCodes(final Method method) { 121 final Code code = method.getCode(); 122 if (code == null) { 123 throw new IllegalStateException(String.format("The method '%s' has no code.", method)); 124 } 125 return code.getCode(); 126 } 127 128 /** 129 * Gets the comparison strategy object. 130 * 131 * @return Comparison strategy object. 132 */ 133 public static BCELComparator<FieldGenOrMethodGen> getComparator() { 134 return bcelComparator; 135 } 136 137 /** 138 * Gets the maximum stack depth of an instruction list by performing control flow analysis. 139 * 140 * @param cp The constant pool generator. 141 * @param il The instruction list. 142 * @param et The exception handlers. 143 * @return maximum stack depth used by method. 144 */ 145 public static int getMaxStack(final ConstantPoolGen cp, final InstructionList il, final CodeExceptionGen[] et) { 146 final BranchStack branchTargets = new BranchStack(); 147 /* 148 * Initially, populate the branch stack with the exception handlers, because these aren't (necessarily) branched to 149 * explicitly. in each case, the stack will have depth 1, containing the exception object. 150 */ 151 for (final CodeExceptionGen element : et) { 152 final InstructionHandle handlerPc = element.getHandlerPC(); 153 if (handlerPc != null) { 154 branchTargets.push(handlerPc, 1); 155 } 156 } 157 int stackDepth = 0; 158 int maxStackDepth = 0; 159 InstructionHandle ih = il.getStart(); 160 while (ih != null) { 161 final Instruction instruction = ih.getInstruction(); 162 final short opcode = instruction.getOpcode(); 163 final int delta = instruction.produceStack(cp) - instruction.consumeStack(cp); 164 stackDepth += delta; 165 if (stackDepth > maxStackDepth) { 166 maxStackDepth = stackDepth; 167 } 168 // choose the next instruction based on whether current is a branch. 169 if (instruction instanceof BranchInstruction) { 170 final BranchInstruction branch = (BranchInstruction) instruction; 171 if (instruction instanceof Select) { 172 // explore all of the select's targets. the default target is handled below. 173 final Select select = (Select) branch; 174 final InstructionHandle[] targets = select.getTargets(); 175 for (final InstructionHandle target : targets) { 176 branchTargets.push(target, stackDepth); 177 } 178 // nothing to fall through to. 179 ih = null; 180 } else if (!(branch instanceof IfInstruction)) { 181 // if an instruction that comes back to following PC, 182 // push next instruction, with stack depth reduced by 1. 183 if (opcode == Const.JSR || opcode == Const.JSR_W) { 184 branchTargets.push(ih.getNext(), stackDepth - 1); 185 } 186 ih = null; 187 } 188 // for all branches, the target of the branch is pushed on the branch stack. 189 // conditional branches have a fall through case, selects don't, and 190 // jsr/jsr_w return to the next instruction. 191 branchTargets.push(branch.getTarget(), stackDepth); 192 } else // check for instructions that terminate the method. 193 if (opcode == Const.ATHROW || opcode == Const.RET || opcode >= Const.IRETURN && opcode <= Const.RETURN) { 194 ih = null; 195 } 196 // normal case, go to the next instruction. 197 if (ih != null) { 198 ih = ih.getNext(); 199 } 200 // if we have no more instructions, see if there are any deferred branches to explore. 201 if (ih == null) { 202 final BranchTarget bt = branchTargets.pop(); 203 if (bt != null) { 204 ih = bt.target; 205 stackDepth = bt.stackDepth; 206 } 207 } 208 } 209 return maxStackDepth; 210 } 211 212 /** 213 * Sets the comparison strategy object. 214 * 215 * @param comparator Comparison strategy object. 216 */ 217 public static void setComparator(final BCELComparator<FieldGenOrMethodGen> comparator) { 218 bcelComparator = comparator; 219 } 220 221 private String className; 222 private Type[] argTypes; 223 private String[] argNames; 224 private int maxLocals; 225 private int maxStack; 226 private InstructionList il; 227 228 private boolean stripAttributes; 229 private boolean compactCodeHeader; 230 private LocalVariableTypeTable localVariableTypeTable; 231 private final List<LocalVariableGen> variableList = new ArrayList<>(); 232 233 private final List<LineNumberGen> lineNumberList = new ArrayList<>(); 234 235 private final List<CodeExceptionGen> exceptionList = new ArrayList<>(); 236 237 private final List<String> throwsList = new ArrayList<>(); 238 239 private final List<Attribute> codeAttrsList = new ArrayList<>(); 240 241 private List<AnnotationEntryGen>[] paramAnnotations; // Array of lists containing AnnotationGen objects 242 243 private boolean hasParameterAnnotations; 244 245 private boolean haveUnpackedParameterAnnotations; 246 247 private List<MethodObserver> observers; 248 249 /** 250 * Declare method. If the method is non-static the constructor automatically declares a local variable '$this' in slot 251 * 0. The actual code is contained in the 'il' parameter, which may further manipulated by the user. But they must take 252 * care not to remove any instruction (handles) that are still referenced from this object. 253 * 254 * For example one may not add a local variable and later remove the instructions it refers to without causing havoc. It 255 * is safe however if you remove that local variable, too. 256 * 257 * @param accessFlags access qualifiers. 258 * @param returnType method type. 259 * @param argTypes argument types. 260 * @param argNames argument names (if this is null, default names will be provided for them). 261 * @param methodName name of method. 262 * @param className class name containing this method (may be null, if you don't care). 263 * @param il instruction list associated with this method, may be null only for abstract or native methods. 264 * @param cp constant pool. 265 */ 266 public MethodGen(final int accessFlags, final Type returnType, final Type[] argTypes, String[] argNames, final String methodName, final String className, 267 final InstructionList il, final ConstantPoolGen cp) { 268 super(accessFlags); 269 setType(returnType); 270 setArgumentTypes(argTypes); 271 setArgumentNames(argNames); 272 setName(methodName); 273 setClassName(className); 274 setInstructionList(il); 275 setConstantPool(cp); 276 final boolean abstract_ = isAbstract() || isNative(); 277 InstructionHandle start = null; 278 final InstructionHandle end = null; 279 if (!abstract_) { 280 start = il.getStart(); 281 // end == null => live to end of method 282 /* 283 * Add local variables, namely the implicit 'this' and the arguments 284 */ 285 if (!isStatic() && className != null) { // Instance method -> 'this' is local var 0 286 addLocalVariable("this", ObjectType.getInstance(className), start, end); 287 } 288 } 289 if (argTypes != null) { 290 final int size = argTypes.length; 291 for (final Type argType : argTypes) { 292 if (Type.VOID == argType) { 293 throw new ClassGenException("'void' is an illegal argument type for a method"); 294 } 295 } 296 if (argNames != null) { // Names for variables provided? 297 if (size != argNames.length) { 298 throw new ClassGenException("Mismatch in argument array lengths: " + size + " versus " + argNames.length); 299 } 300 } else { // Give them dummy names 301 argNames = new String[size]; 302 for (int i = 0; i < size; i++) { 303 argNames[i] = "arg" + i; 304 } 305 setArgumentNames(argNames); 306 } 307 if (!abstract_) { 308 for (int i = 0; i < size; i++) { 309 addLocalVariable(argNames[i], argTypes[i], start, end); 310 } 311 } 312 } 313 } 314 315 /** 316 * Instantiate from existing method. 317 * 318 * @param method method. 319 * @param className class name containing this method. 320 * @param cp constant pool. 321 */ 322 public MethodGen(final Method method, final String className, final ConstantPoolGen cp) { 323 this(method.getAccessFlags(), Type.getReturnType(method.getSignature()), Type.getArgumentTypes(method.getSignature()), 324 null /* may be overridden anyway */ 325 , method.getName(), className, 326 (method.getAccessFlags() & (Const.ACC_ABSTRACT | Const.ACC_NATIVE)) == 0 ? new InstructionList(getByteCodes(method)) : null, cp); 327 final Attribute[] attributes = method.getAttributes(); 328 for (final Attribute attribute : attributes) { 329 Attribute a = attribute; 330 if (a instanceof Code) { 331 final Code c = (Code) a; 332 compactCodeHeader = c.isCompactCodeHeader(); 333 setMaxStack(c.getMaxStack()); 334 setMaxLocals(c.getMaxLocals()); 335 final CodeException[] ces = c.getExceptionTable(); 336 if (ces != null) { 337 for (final CodeException ce : ces) { 338 final int type = ce.getCatchType(); 339 ObjectType cType = null; 340 if (type > 0) { 341 final String cen = method.getConstantPool().getConstantString(type, Const.CONSTANT_Class); 342 cType = ObjectType.getInstance(cen); 343 } 344 final int endPc = ce.getEndPC(); 345 final int length = getByteCodes(method).length; 346 InstructionHandle end; 347 if (length == endPc) { // May happen, because end_pc is exclusive 348 end = il.getEnd(); 349 } else { 350 end = il.findHandle(endPc); 351 end = end.getPrev(); // Make it inclusive 352 } 353 addExceptionHandler(il.findHandle(ce.getStartPC()), end, il.findHandle(ce.getHandlerPC()), cType); 354 } 355 } 356 final Attribute[] cAttributes = c.getAttributes(); 357 for (final Attribute cAttribute : cAttributes) { 358 a = cAttribute; 359 if (a instanceof LineNumberTable) { 360 ((LineNumberTable) a).forEach(l -> { 361 final InstructionHandle ih = il.findHandle(l.getStartPC()); 362 if (ih != null) { 363 addLineNumber(ih, l.getLineNumber()); 364 } 365 }); 366 } else if (a instanceof LocalVariableTable) { 367 updateLocalVariableTable((LocalVariableTable) a); 368 } else if (a instanceof LocalVariableTypeTable) { 369 this.localVariableTypeTable = (LocalVariableTypeTable) a.copy(cp.getConstantPool()); 370 } else { 371 addCodeAttribute(a); 372 } 373 } 374 } else if (a instanceof ExceptionTable) { 375 Collections.addAll(throwsList, ((ExceptionTable) a).getExceptionNames()); 376 } else if (a instanceof Annotations) { 377 final Annotations runtimeAnnotations = (Annotations) a; 378 runtimeAnnotations.forEach(element -> addAnnotationEntry(new AnnotationEntryGen(element, cp, false))); 379 } else { 380 addAttribute(a); 381 } 382 } 383 } 384 385 /** 386 * Adds annotations as an attribute. 387 * 388 * @param cp The constant pool generator. 389 * @since 6.0 390 */ 391 public void addAnnotationsAsAttribute(final ConstantPoolGen cp) { 392 addAll(AnnotationEntryGen.getAnnotationAttributes(cp, super.getAnnotationEntries())); 393 } 394 395 /** 396 * Add an attribute to the code. Currently, the JVM knows about the LineNumberTable, LocalVariableTable and StackMap 397 * attributes, where the former two will be generated automatically and the latter is used for the MIDP only. Other 398 * attributes will be ignored by the JVM but do no harm. 399 * 400 * @param a attribute to be added. 401 */ 402 public void addCodeAttribute(final Attribute a) { 403 codeAttrsList.add(a); 404 } 405 406 /** 407 * Add an exception possibly thrown by this method. 408 * 409 * @param className (fully qualified) name of exception. 410 */ 411 public void addException(final String className) { 412 throwsList.add(className); 413 } 414 415 /** 416 * Add an exception handler, that is, specify region where a handler is active and an instruction where the actual handling 417 * is done. 418 * 419 * @param startPc Start of region (inclusive). 420 * @param endPc End of region (inclusive). 421 * @param handlerPc Where handling is done. 422 * @param catchType class type of handled exception or null if any exception is handled. 423 * @return new exception handler object. 424 */ 425 public CodeExceptionGen addExceptionHandler(final InstructionHandle startPc, final InstructionHandle endPc, final InstructionHandle handlerPc, 426 final ObjectType catchType) { 427 if (startPc == null || endPc == null || handlerPc == null) { 428 throw new ClassGenException("Exception handler target is a null instruction"); 429 } 430 final CodeExceptionGen c = new CodeExceptionGen(startPc, endPc, handlerPc, catchType); 431 exceptionList.add(c); 432 return c; 433 } 434 435 /** 436 * Give an instruction a line number corresponding to the source code line. 437 * 438 * @param ih instruction to tag. 439 * @param srcLine The source line number. 440 * @return new line number object. 441 * @see LineNumber 442 */ 443 public LineNumberGen addLineNumber(final InstructionHandle ih, final int srcLine) { 444 final LineNumberGen l = new LineNumberGen(ih, srcLine); 445 lineNumberList.add(l); 446 return l; 447 } 448 449 /** 450 * Adds a local variable to this method and assigns an index automatically. 451 * 452 * @param name variable name. 453 * @param type variable type. 454 * @param start from where the variable is valid, if this is null, it is valid from the start. 455 * @param end until where the variable is valid, if this is null, it is valid to the end. 456 * @return new local variable object. 457 * @see LocalVariable 458 */ 459 public LocalVariableGen addLocalVariable(final String name, final Type type, final InstructionHandle start, final InstructionHandle end) { 460 return addLocalVariable(name, type, maxLocals, start, end); 461 } 462 463 /** 464 * Adds a local variable to this method. 465 * 466 * @param name variable name. 467 * @param type variable type. 468 * @param slot The index of the local variable, if type is long or double, the next available index is slot+2. 469 * @param start from where the variable is valid. 470 * @param end until where the variable is valid. 471 * @return new local variable object. 472 * @see LocalVariable 473 */ 474 public LocalVariableGen addLocalVariable(final String name, final Type type, final int slot, final InstructionHandle start, final InstructionHandle end) { 475 return addLocalVariable(name, type, slot, start, end, slot); 476 } 477 478 /** 479 * Adds a local variable to this method. 480 * 481 * @param name variable name. 482 * @param type variable type. 483 * @param slot The index of the local variable, if type is long or double, the next available index is slot+2. 484 * @param start from where the variable is valid. 485 * @param end until where the variable is valid. 486 * @param origIndex The index of the local variable prior to any modifications. 487 * @return new local variable object. 488 * @see LocalVariable 489 */ 490 public LocalVariableGen addLocalVariable(final String name, final Type type, final int slot, final InstructionHandle start, final InstructionHandle end, 491 final int origIndex) { 492 final byte t = type.getType(); 493 if (t != Const.T_ADDRESS) { 494 final int add = type.getSize(); 495 if (slot + add > maxLocals) { 496 maxLocals = slot + add; 497 } 498 final LocalVariableGen l = new LocalVariableGen(slot, name, type, start, end, origIndex); 499 final int i; 500 if ((i = variableList.indexOf(l)) >= 0) { 501 variableList.set(i, l); 502 } else { 503 variableList.add(l); 504 } 505 return l; 506 } 507 throw new IllegalArgumentException("Cannot use " + type + " as the type of a local variable"); 508 } 509 510 /** 511 * Add observer for this object. 512 * 513 * @param o The observer to add. 514 */ 515 public void addObserver(final MethodObserver o) { 516 if (observers == null) { 517 observers = new ArrayList<>(); 518 } 519 observers.add(o); 520 } 521 522 /** 523 * Adds a parameter annotation. 524 * 525 * @param parameterIndex The parameter index. 526 * @param annotation The annotation. 527 */ 528 public void addParameterAnnotation(final int parameterIndex, final AnnotationEntryGen annotation) { 529 ensureExistingParameterAnnotationsUnpacked(); 530 if (!hasParameterAnnotations) { 531 @SuppressWarnings("unchecked") // OK 532 final List<AnnotationEntryGen>[] parmList = new List[argTypes.length]; 533 paramAnnotations = parmList; 534 hasParameterAnnotations = true; 535 } 536 final List<AnnotationEntryGen> existingAnnotations = paramAnnotations[parameterIndex]; 537 if (existingAnnotations != null) { 538 existingAnnotations.add(annotation); 539 } else { 540 final List<AnnotationEntryGen> l = new ArrayList<>(); 541 l.add(annotation); 542 paramAnnotations[parameterIndex] = l; 543 } 544 } 545 546 /** 547 * Adds parameter annotations as an attribute. 548 * 549 * @param cp The constant pool generator. 550 * @since 6.0 551 */ 552 public void addParameterAnnotationsAsAttribute(final ConstantPoolGen cp) { 553 if (!hasParameterAnnotations) { 554 return; 555 } 556 final Attribute[] attrs = AnnotationEntryGen.getParameterAnnotationAttributes(cp, paramAnnotations); 557 if (attrs != null) { 558 addAll(attrs); 559 } 560 } 561 562 private Attribute[] addRuntimeAnnotationsAsAttribute(final ConstantPoolGen cp) { 563 final Attribute[] attrs = AnnotationEntryGen.getAnnotationAttributes(cp, super.getAnnotationEntries()); 564 addAll(attrs); 565 return attrs; 566 } 567 568 private Attribute[] addRuntimeParameterAnnotationsAsAttribute(final ConstantPoolGen cp) { 569 if (!hasParameterAnnotations) { 570 return Attribute.EMPTY_ARRAY; 571 } 572 final Attribute[] attrs = AnnotationEntryGen.getParameterAnnotationAttributes(cp, paramAnnotations); 573 addAll(attrs); 574 return attrs; 575 } 576 577 private void adjustLocalVariableTypeTable(final LocalVariableTable lvt) { 578 final LocalVariable[] lv = lvt.getLocalVariableTable(); 579 for (final LocalVariable element : localVariableTypeTable.getLocalVariableTypeTable()) { 580 for (final LocalVariable l : lv) { 581 if (element.getName().equals(l.getName()) && element.getIndex() == l.getOrigIndex()) { 582 element.setLength(l.getLength()); 583 element.setStartPC(l.getStartPC()); 584 element.setIndex(l.getIndex()); 585 break; 586 } 587 } 588 } 589 } 590 591 /** 592 * Creates a deep copy of this method. 593 * 594 * @param className The class name. 595 * @param cp The constant pool generator. 596 * @return deep copy of this method. 597 */ 598 public MethodGen copy(final String className, final ConstantPoolGen cp) { 599 final Method m = ((MethodGen) clone()).getMethod(); 600 final MethodGen mg = new MethodGen(m, className, super.getConstantPool()); 601 if (super.getConstantPool() != cp) { 602 mg.setConstantPool(cp); 603 mg.getInstructionList().replaceConstantPool(super.getConstantPool(), cp); 604 } 605 return mg; 606 } 607 608 /** 609 * Goes through the attributes on the method and identifies any that are RuntimeParameterAnnotations, extracting their 610 * contents and storing them as parameter annotations. There are two kinds of parameter annotation - visible and 611 * invisible. Once they have been unpacked, these attributes are deleted. (The annotations will be rebuilt as attributes 612 * when someone builds a Method object out of this MethodGen object). 613 */ 614 private void ensureExistingParameterAnnotationsUnpacked() { 615 if (haveUnpackedParameterAnnotations) { 616 return; 617 } 618 // Find attributes that contain parameter annotation data 619 final Attribute[] attrs = getAttributes(); 620 ParameterAnnotations paramAnnVisAttr = null; 621 ParameterAnnotations paramAnnInvisAttr = null; 622 for (final Attribute attribute : attrs) { 623 if (attribute instanceof ParameterAnnotations) { 624 // Initialize paramAnnotations 625 if (!hasParameterAnnotations) { 626 @SuppressWarnings("unchecked") // OK 627 final List<AnnotationEntryGen>[] parmList = new List[argTypes.length]; 628 paramAnnotations = parmList; 629 Arrays.setAll(paramAnnotations, i -> new ArrayList<>()); 630 } 631 hasParameterAnnotations = true; 632 final ParameterAnnotations rpa = (ParameterAnnotations) attribute; 633 if (rpa instanceof RuntimeVisibleParameterAnnotations) { 634 paramAnnVisAttr = rpa; 635 } else { 636 paramAnnInvisAttr = rpa; 637 } 638 final ParameterAnnotationEntry[] parameterAnnotationEntries = rpa.getParameterAnnotationEntries(); 639 for (int j = 0; j < parameterAnnotationEntries.length; j++) { 640 // This returns Annotation[] ... 641 final ParameterAnnotationEntry immutableArray = rpa.getParameterAnnotationEntries()[j]; 642 // ... which needs transforming into an AnnotationGen[] ... 643 final List<AnnotationEntryGen> mutable = makeMutableVersion(immutableArray.getAnnotationEntries()); 644 // ... then add these to any we already know about 645 paramAnnotations[j].addAll(mutable); 646 } 647 } 648 } 649 if (paramAnnVisAttr != null) { 650 removeAttribute(paramAnnVisAttr); 651 } 652 if (paramAnnInvisAttr != null) { 653 removeAttribute(paramAnnInvisAttr); 654 } 655 haveUnpackedParameterAnnotations = true; 656 } 657 658 /** 659 * Return value as defined by given BCELComparator strategy. By default two MethodGen objects are said to be equal when 660 * their names and signatures are equal. 661 * 662 * @see Object#equals(Object) 663 */ 664 @Override 665 public boolean equals(final Object obj) { 666 return obj instanceof FieldGenOrMethodGen && bcelComparator.equals(this, (FieldGenOrMethodGen) obj); 667 } 668 669 // J5TODO: Should paramAnnotations be an array of arrays? Rather than an array of lists, this 670 // is more likely to suggest to the caller it is readonly (which a List does not). 671 672 /** 673 * Gets a list of AnnotationGen objects representing parameter annotations. 674 * 675 * @param i The parameter index. 676 * @return list of AnnotationGen objects. 677 * @since 6.0 678 */ 679 public List<AnnotationEntryGen> getAnnotationsOnParameter(final int i) { 680 ensureExistingParameterAnnotationsUnpacked(); 681 if (!hasParameterAnnotations || i > argTypes.length) { 682 return null; 683 } 684 return paramAnnotations[i]; 685 } 686 687 /** 688 * Gets the argument name at the specified index. 689 * 690 * @param i The argument index. 691 * @return The argument name. 692 */ 693 public String getArgumentName(final int i) { 694 return argNames[i]; 695 } 696 697 /** 698 * Gets all argument names. 699 * 700 * @return array of argument names. 701 */ 702 public String[] getArgumentNames() { 703 return argNames.clone(); 704 } 705 706 /** 707 * Gets the argument type at the specified index. 708 * 709 * @param i The argument index. 710 * @return The argument type. 711 */ 712 public Type getArgumentType(final int i) { 713 return argTypes[i]; 714 } 715 716 /** 717 * Gets all argument types. 718 * 719 * @return array of argument types. 720 */ 721 public Type[] getArgumentTypes() { 722 return argTypes.clone(); 723 } 724 725 /** 726 * Gets the class that contains this method. 727 * 728 * @return class that contains this method. 729 */ 730 public String getClassName() { 731 return className; 732 } 733 734 /** 735 * Gets all attributes of this method. 736 * 737 * @return all attributes of this method. 738 */ 739 public Attribute[] getCodeAttributes() { 740 return codeAttrsList.toArray(Attribute.EMPTY_ARRAY); 741 } 742 743 /** 744 * Gets the exception handlers for the 'Code' attribute. 745 * 746 * @return code exceptions for 'Code' attribute. 747 */ 748 private CodeException[] getCodeExceptions() { 749 final int size = exceptionList.size(); 750 final CodeException[] cExc = new CodeException[size]; 751 Arrays.setAll(cExc, i -> exceptionList.get(i).getCodeException(super.getConstantPool())); 752 return cExc; 753 } 754 755 /** 756 * Gets an array of declared exception handlers. 757 * 758 * @return array of declared exception handlers. 759 */ 760 public CodeExceptionGen[] getExceptionHandlers() { 761 return exceptionList.toArray(CodeExceptionGen.EMPTY_ARRAY); 762 } 763 764 /** 765 * Gets an array of thrown exceptions. 766 * 767 * @return array of thrown exceptions. 768 */ 769 public String[] getExceptions() { 770 return throwsList.toArray(ArrayUtils.EMPTY_STRING_ARRAY); 771 } 772 773 /** 774 * Gets the 'Exceptions' attribute of all the exceptions thrown by this method. 775 * 776 * @return 'Exceptions' attribute of all the exceptions thrown by this method. 777 */ 778 private ExceptionTable getExceptionTable(final ConstantPoolGen cp) { 779 final int size = throwsList.size(); 780 final int[] ex = new int[size]; 781 Arrays.setAll(ex, i -> cp.addClass(throwsList.get(i))); 782 return new ExceptionTable(cp.addUtf8("Exceptions"), 2 + 2 * size, ex, cp.getConstantPool()); 783 } 784 785 /** 786 * Gets the instruction list. 787 * 788 * @return The instruction list. 789 */ 790 public InstructionList getInstructionList() { 791 return il; 792 } 793 794 /** 795 * Gets an array of line numbers. 796 * 797 * @return array of line numbers. 798 */ 799 public LineNumberGen[] getLineNumbers() { 800 return lineNumberList.toArray(LineNumberGen.EMPTY_ARRAY); 801 } 802 803 /** 804 * Gets the 'LineNumberTable' attribute of all the local variables of this method. 805 * 806 * @param cp The constant pool generator. 807 * @return 'LineNumberTable' attribute of all the local variables of this method. 808 */ 809 public LineNumberTable getLineNumberTable(final ConstantPoolGen cp) { 810 final int size = lineNumberList.size(); 811 final LineNumber[] ln = new LineNumber[size]; 812 Arrays.setAll(ln, i -> lineNumberList.get(i).getLineNumber()); 813 return new LineNumberTable(cp.addUtf8("LineNumberTable"), 2 + ln.length * 4, ln, cp.getConstantPool()); 814 } 815 816 /** 817 * Gets an array of declared local variables sorted by index. 818 * 819 * If the range of the variable has not been set yet, it will be set to be valid from the start to the end of the 820 * instruction list. 821 * 822 * @return array of declared local variables sorted by index. 823 */ 824 public LocalVariableGen[] getLocalVariables() { 825 final int size = variableList.size(); 826 final LocalVariableGen[] lg = new LocalVariableGen[size]; 827 variableList.toArray(lg); 828 for (int i = 0; i < size; i++) { 829 if (lg[i].getStart() == null && il != null) { 830 lg[i].setStart(il.getStart()); 831 } 832 if (lg[i].getEnd() == null && il != null) { 833 lg[i].setEnd(il.getEnd()); 834 } 835 } 836 if (size > 1) { 837 Arrays.sort(lg, Comparator.comparingInt(LocalVariableGen::getIndex)); 838 } 839 return lg; 840 } 841 842 /** 843 * Gets the 'LocalVariableTable' attribute of all the local variables of this method. 844 * 845 * @param cp The constant pool generator. 846 * @return 'LocalVariableTable' attribute of all the local variables of this method. 847 */ 848 public LocalVariableTable getLocalVariableTable(final ConstantPoolGen cp) { 849 final LocalVariableGen[] lg = getLocalVariables(); 850 final int size = lg.length; 851 final LocalVariable[] lv = new LocalVariable[size]; 852 Arrays.setAll(lv, i -> lg[i].getLocalVariable(cp)); 853 return new LocalVariableTable(cp.addUtf8("LocalVariableTable"), 2 + lv.length * 10, lv, cp.getConstantPool()); 854 } 855 856 /** 857 * Gets the 'LocalVariableTypeTable' attribute of this method. 858 * 859 * @return 'LocalVariableTypeTable' attribute of this method. 860 */ 861 public LocalVariableTypeTable getLocalVariableTypeTable() { 862 return localVariableTypeTable; 863 } 864 865 /** 866 * Gets the maximum number of local variables. 867 * 868 * @return The maximum number of local variables. 869 */ 870 public int getMaxLocals() { 871 return maxLocals; 872 } 873 874 /** 875 * Gets the maximum stack size. 876 * 877 * @return The maximum stack size. 878 */ 879 public int getMaxStack() { 880 return maxStack; 881 } 882 883 /** 884 * Gets method object. Never forget to call setMaxStack() or setMaxStack(max), respectively, before calling this method 885 * (the same applies for max locals). 886 * 887 * <p> 888 * When initialized from an existing method, the Code header layout is preserved. Use {@link #setCompactCodeHeader(boolean)} to change it. 889 * </p> 890 * 891 * @return method object. 892 * @throws org.apache.bcel.classfile.ClassFormatException Thrown if Code values do not fit the selected header layout. 893 */ 894 public Method getMethod() { 895 final String signature = getSignature(); 896 final ConstantPoolGen cp = super.getConstantPool(); 897 final int nameIndex = cp.addUtf8(super.getName()); 898 final int signatureIndex = cp.addUtf8(signature); 899 /* 900 * Also updates positions of instructions, that is, their indices 901 */ 902 final byte[] byteCode = il != null ? il.getByteCode() : null; 903 LineNumberTable lnt = null; 904 LocalVariableTable lvt = null; 905 /* 906 * Create LocalVariableTable and LineNumberTable attributes (for debuggers, for example) 907 */ 908 if (!variableList.isEmpty() && !stripAttributes) { 909 updateLocalVariableTable(getLocalVariableTable(cp)); 910 lvt = getLocalVariableTable(cp); 911 } 912 // Updating the local variable table can increase maxLocals. Validate before adding temporary attributes. 913 if (compactCodeHeader && byteCode != null && !isAbstract() && !isNative()) { 914 Args.requireU1(maxStack, "maxStack"); 915 Args.requireU1(maxLocals, "maxLocals"); 916 Args.requireU2(byteCode.length, "Code length attribute"); 917 } 918 if (lvt != null) { 919 addCodeAttribute(lvt); 920 } 921 if (localVariableTypeTable != null) { 922 // LocalVariable length in LocalVariableTypeTable is not updated automatically. It's a difference with 923 // LocalVariableTable. 924 if (lvt != null) { 925 adjustLocalVariableTypeTable(lvt); 926 } 927 addCodeAttribute(localVariableTypeTable); 928 } 929 if (!lineNumberList.isEmpty() && !stripAttributes) { 930 addCodeAttribute(lnt = getLineNumberTable(cp)); 931 } 932 final Attribute[] codeAttrs = getCodeAttributes(); 933 /* 934 * Each attribute causes 6 additional header bytes 935 */ 936 int attrsLen = 0; 937 for (final Attribute codeAttr : codeAttrs) { 938 attrsLen += codeAttr.getLength() + 6; 939 } 940 final CodeException[] cExc = getCodeExceptions(); 941 final int excLen = cExc.length * 8; // Every entry takes 8 bytes 942 Code code = null; 943 if (byteCode != null && !isAbstract() && !isNative()) { 944 // Remove any stale code attribute 945 final Attribute[] attributes = getAttributes(); 946 for (final Attribute a : attributes) { 947 if (a instanceof Code) { 948 removeAttribute(a); 949 } 950 } 951 code = new Code(cp.addUtf8("Code"), 8 + byteCode.length + // prologue byte code 952 2 + excLen + // exceptions 953 2 + attrsLen, // attributes 954 maxStack, maxLocals, byteCode, cExc, codeAttrs, cp.getConstantPool()); 955 code.setCompactCodeHeader(compactCodeHeader); 956 addAttribute(code); 957 } 958 final Attribute[] annotations = addRuntimeAnnotationsAsAttribute(cp); 959 final Attribute[] parameterAnnotations = addRuntimeParameterAnnotationsAsAttribute(cp); 960 ExceptionTable et = null; 961 if (!throwsList.isEmpty()) { 962 addAttribute(et = getExceptionTable(cp)); 963 // Add 'Exceptions' if there are "throws" clauses 964 } 965 final Method m = new Method(super.getAccessFlags(), nameIndex, signatureIndex, getAttributes(), cp.getConstantPool()); 966 // Undo effects of adding attributes 967 if (lvt != null) { 968 removeCodeAttribute(lvt); 969 } 970 if (localVariableTypeTable != null) { 971 removeCodeAttribute(localVariableTypeTable); 972 } 973 if (lnt != null) { 974 removeCodeAttribute(lnt); 975 } 976 if (code != null) { 977 removeAttribute(code); 978 } 979 if (et != null) { 980 removeAttribute(et); 981 } 982 removeRuntimeAttributes(annotations); 983 removeRuntimeAttributes(parameterAnnotations); 984 return m; 985 } 986 987 /** 988 * Gets the return type. 989 * 990 * @return The return type. 991 */ 992 public Type getReturnType() { 993 return getType(); 994 } 995 996 @Override 997 public String getSignature() { 998 return Type.getMethodSignature(super.getType(), argTypes); 999 } 1000 1001 /** 1002 * Return value as defined by given BCELComparator strategy. By default return the hash code of the method's name XOR 1003 * signature. 1004 * 1005 * @see Object#hashCode() 1006 */ 1007 @Override 1008 public int hashCode() { 1009 return bcelComparator.hashCode(this); 1010 } 1011 1012 /** 1013 * Gets whether generated Code attributes use compact four-byte headers. 1014 * 1015 * @return Whether generated Code attributes use compact headers. 1016 * @since 6.14.0 1017 * @see Code#isCompactCodeHeader() 1018 */ 1019 public boolean isCompactCodeHeader() { 1020 return compactCodeHeader; 1021 } 1022 1023 private List<AnnotationEntryGen> makeMutableVersion(final AnnotationEntry[] mutableArray) { 1024 return Streams.of(mutableArray).map(ae -> new AnnotationEntryGen(ae, getConstantPool(), false)).collect(Collectors.toList()); 1025 } 1026 1027 /** 1028 * Remove a code attribute. 1029 * 1030 * @param a The attribute to remove. 1031 */ 1032 public void removeCodeAttribute(final Attribute a) { 1033 codeAttrsList.remove(a); 1034 } 1035 1036 /** 1037 * Remove all code attributes. 1038 */ 1039 public void removeCodeAttributes() { 1040 localVariableTypeTable = null; 1041 codeAttrsList.clear(); 1042 } 1043 1044 /** 1045 * Remove an exception. 1046 * 1047 * @param c The exception to remove. 1048 */ 1049 public void removeException(final String c) { 1050 throwsList.remove(c); 1051 } 1052 1053 /** 1054 * Remove an exception handler. 1055 * 1056 * @param c The exception handler to remove. 1057 */ 1058 public void removeExceptionHandler(final CodeExceptionGen c) { 1059 exceptionList.remove(c); 1060 } 1061 1062 /** 1063 * Remove all line numbers. 1064 */ 1065 public void removeExceptionHandlers() { 1066 exceptionList.clear(); 1067 } 1068 1069 /** 1070 * Remove all exceptions. 1071 */ 1072 public void removeExceptions() { 1073 throwsList.clear(); 1074 } 1075 1076 /** 1077 * Remove a line number. 1078 * 1079 * @param l The line number to remove. 1080 */ 1081 public void removeLineNumber(final LineNumberGen l) { 1082 lineNumberList.remove(l); 1083 } 1084 1085 /** 1086 * Remove all line numbers. 1087 */ 1088 public void removeLineNumbers() { 1089 lineNumberList.clear(); 1090 } 1091 1092 /** 1093 * Remove a local variable, its slot will not be reused, if you do not use addLocalVariable with an explicit index 1094 * argument. 1095 * 1096 * @param l The local variable to remove. 1097 */ 1098 public void removeLocalVariable(final LocalVariableGen l) { 1099 l.dispose(); 1100 variableList.remove(l); 1101 } 1102 1103 /** 1104 * Remove all local variables. 1105 */ 1106 public void removeLocalVariables() { 1107 variableList.forEach(LocalVariableGen::dispose); 1108 variableList.clear(); 1109 } 1110 1111 /** 1112 * Remove the LocalVariableTypeTable 1113 */ 1114 public void removeLocalVariableTypeTable() { 1115 localVariableTypeTable = null; 1116 } 1117 1118 /** 1119 * Remove all NOPs from the instruction list (if possible) and update every object referring to them, that is, branch 1120 * instructions, local variables and exception handlers. 1121 */ 1122 public void removeNOPs() { 1123 if (il != null) { 1124 InstructionHandle next; 1125 /* 1126 * Check branch instructions. 1127 */ 1128 for (InstructionHandle ih = il.getStart(); ih != null; ih = next) { 1129 next = ih.getNext(); 1130 if (next != null && ih.getInstruction() instanceof NOP) { 1131 try { 1132 il.delete(ih); 1133 } catch (final TargetLostException e) { 1134 for (final InstructionHandle target : e.getTargets()) { 1135 for (final InstructionTargeter targeter : target.getTargeters()) { 1136 targeter.updateTarget(target, next); 1137 } 1138 } 1139 } 1140 } 1141 } 1142 } 1143 } 1144 1145 /** 1146 * Remove observer for this object. 1147 * 1148 * @param o The observer to remove. 1149 */ 1150 public void removeObserver(final MethodObserver o) { 1151 if (observers != null) { 1152 observers.remove(o); 1153 } 1154 } 1155 1156 /** 1157 * Would prefer to make this private, but need a way to test if client is using BCEL version 6.5.0 or later that 1158 * contains fix for BCEL-329. 1159 * 1160 * @param attributes The attributes to remove. 1161 * @since 6.5.0 1162 */ 1163 public void removeRuntimeAttributes(final Attribute[] attributes) { 1164 Streams.of(attributes).forEach(this::removeAttribute); 1165 } 1166 1167 /** 1168 * Sets the argument name at the specified index. 1169 * 1170 * @param i The argument index. 1171 * @param name The argument name. 1172 */ 1173 public void setArgumentName(final int i, final String name) { 1174 argNames[i] = name; 1175 } 1176 1177 /** 1178 * Sets all argument names. 1179 * 1180 * @param argNames The argument names. 1181 */ 1182 public void setArgumentNames(final String[] argNames) { 1183 this.argNames = ArrayUtils.nullToEmpty(argNames); 1184 } 1185 1186 /** 1187 * Sets the argument type at the specified index. 1188 * 1189 * @param i The argument index. 1190 * @param type The argument type. 1191 */ 1192 public void setArgumentType(final int i, final Type type) { 1193 argTypes[i] = type; 1194 } 1195 1196 /** 1197 * Sets all argument types. 1198 * 1199 * @param argTypes The argument types. 1200 */ 1201 public void setArgumentTypes(final Type[] argTypes) { 1202 this.argTypes = argTypes != null ? argTypes : Type.NO_ARGS; 1203 } 1204 1205 /** 1206 * Sets the class name. 1207 * 1208 * @param className The class name. 1209 */ 1210 public void setClassName(final String className) { // TODO could be package-protected? 1211 this.className = className; 1212 } 1213 1214 /** 1215 * Sets the header layout of generated Code attributes. 1216 * 1217 * <p> 1218 * Compact headers encode {@code max_stack}, {@code max_locals}, and {@code code_length} as unsigned one-, one-, and two-byte fields, respectively, 1219 * totaling 4 bytes instead of the standard 8. This changes only these field widths, not the bytecode or the remaining attribute layout. 1220 * Compact headers are intended for historical class file versions 45.0 through 45.2. 1221 * </p> 1222 * 1223 * <p> 1224 * Newly created methods use standard headers by default. Methods initialized from an existing method preserve its header layout. 1225 * {@link #getMethod()} checks that compact headers have {@code max_stack} and {@code max_locals} from 0 through 255 and a code length 1226 * of at most 65535 bytes. 1227 * </p> 1228 * 1229 * <p> 1230 * When dumping the enclosing class, {@link org.apache.bcel.classfile.JavaClass#dump(java.io.DataOutputStream)} may select a different layout 1231 * for consistency with the class version and its other methods. This does not change the generated Code attribute's header setting. 1232 * </p> 1233 * 1234 * @param compactCodeHeader Whether to generate compact four-byte Code headers ({@code true}) or standard eight-byte headers ({@code false}). 1235 * @since 6.14.0 1236 * @see Code#setCompactCodeHeader(boolean) 1237 */ 1238 public void setCompactCodeHeader(final boolean compactCodeHeader) { 1239 this.compactCodeHeader = compactCodeHeader; 1240 } 1241 1242 /** 1243 * Sets the instruction list. 1244 * 1245 * @param il The instruction list. 1246 */ 1247 public void setInstructionList(final InstructionList il) { // TODO could be package-protected? 1248 this.il = il; 1249 } 1250 1251 /** 1252 * Sets the maximum number of local variables by examining the method signature, the instructions and the local 1253 * variable table entries added via {@link #addLocalVariable}. 1254 */ 1255 public void setMaxLocals() { // TODO could be package-protected? (some tests would need repackaging) 1256 if (il != null) { 1257 int max = isStatic() ? 0 : 1; 1258 for (final Type argType : argTypes) { 1259 max += argType.getSize(); 1260 } 1261 for (InstructionHandle ih = il.getStart(); ih != null; ih = ih.getNext()) { 1262 final Instruction ins = ih.getInstruction(); 1263 if (ins instanceof LocalVariableInstruction || ins instanceof RET || ins instanceof IINC) { 1264 final int index = ((IndexedInstruction) ins).getIndex() + ((TypedInstruction) ins).getType(super.getConstantPool()).getSize(); 1265 if (index > max) { 1266 max = index; 1267 } 1268 } 1269 } 1270 // Local variables may be registered (for example, copied from an existing LocalVariableTable) without 1271 // being referenced by any instruction, for example unused variables emitted by some compilers. Account for 1272 // those too, so a later call does not shrink maxLocals below what those entries require. 1273 for (final LocalVariableGen lv : variableList) { 1274 final int index = lv.getIndex() + lv.getType().getSize(); 1275 if (index > max) { 1276 max = index; 1277 } 1278 } 1279 maxLocals = max; 1280 } else { 1281 maxLocals = 0; 1282 } 1283 } 1284 1285 /** 1286 * Sets maximum number of local variables. 1287 * 1288 * @param m The maximum number of local variables. 1289 */ 1290 public void setMaxLocals(final int m) { 1291 maxLocals = m; 1292 } 1293 1294 /** 1295 * Sets the maximum stack size by performing control flow analysis. 1296 */ 1297 public void setMaxStack() { // TODO could be package-protected? (some tests would need repackaging) 1298 if (il != null) { 1299 maxStack = getMaxStack(super.getConstantPool(), il, getExceptionHandlers()); 1300 } else { 1301 maxStack = 0; 1302 } 1303 } 1304 1305 /** 1306 * Sets maximum stack size for this method. 1307 * 1308 * @param m The maximum stack size. 1309 */ 1310 public void setMaxStack(final int m) { // TODO could be package-protected? 1311 maxStack = m; 1312 } 1313 1314 /** 1315 * Sets the return type. 1316 * 1317 * @param returnType The return type. 1318 */ 1319 public void setReturnType(final Type returnType) { 1320 setType(returnType); 1321 } 1322 1323 /** 1324 * Do not/Do produce attributes code attributesLineNumberTable and LocalVariableTable, like javac -O. 1325 * 1326 * @param flag whether to strip attributes. 1327 */ 1328 public void stripAttributes(final boolean flag) { 1329 stripAttributes = flag; 1330 } 1331 1332 /** 1333 * Return string representation close to declaration format, 'public static void main(String[]) throws IOException', 1334 * for example. 1335 * 1336 * @return String representation of the method. 1337 */ 1338 @Override 1339 public final String toString() { 1340 final String access = Utility.accessToString(super.getAccessFlags()); 1341 String signature = Type.getMethodSignature(super.getType(), argTypes); 1342 signature = Utility.methodSignatureToString(signature, super.getName(), access, true, getLocalVariableTable(super.getConstantPool())); 1343 final StringBuilder buf = new StringBuilder(signature); 1344 for (final Attribute a : getAttributes()) { 1345 if (!(a instanceof Code || a instanceof ExceptionTable)) { 1346 buf.append(" [").append(a).append("]"); 1347 } 1348 } 1349 1350 if (!throwsList.isEmpty()) { 1351 for (final String throwsDescriptor : throwsList) { 1352 buf.append("\n\t\tthrows ").append(throwsDescriptor); 1353 } 1354 } 1355 return buf.toString(); 1356 } 1357 1358 /** 1359 * Call notify() method on all observers. This method is not called automatically whenever the state has changed, but 1360 * has to be called by the user after they have finished editing the object. 1361 */ 1362 public void update() { 1363 if (observers != null) { 1364 for (final MethodObserver observer : observers) { 1365 observer.notify(this); 1366 } 1367 } 1368 } 1369 1370 private void updateLocalVariableTable(final LocalVariableTable a) { 1371 removeLocalVariables(); 1372 for (final LocalVariable l : a.getLocalVariableTable()) { 1373 InstructionHandle start = il.findHandle(l.getStartPC()); 1374 final InstructionHandle end = il.findHandle(l.getStartPC() + l.getLength()); 1375 // Repair malformed handles 1376 if (start == null) { 1377 start = il.getStart(); 1378 } 1379 // end == null => live to end of method 1380 // Since we are recreating the LocalVaraible, we must 1381 // propagate the orig_index to new copy. 1382 addLocalVariable(l.getName(), Type.getType(l.getSignature()), l.getIndex(), start, end, l.getOrigIndex()); 1383 } 1384 } 1385} 1386