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