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