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