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