View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.bcel.generic;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.Comparator;
25  import java.util.Hashtable;
26  import java.util.List;
27  import java.util.Objects;
28  import java.util.Stack;
29  import java.util.stream.Collectors;
30  
31  import org.apache.bcel.Const;
32  import org.apache.bcel.classfile.AnnotationEntry;
33  import org.apache.bcel.classfile.Annotations;
34  import org.apache.bcel.classfile.Attribute;
35  import org.apache.bcel.classfile.Code;
36  import org.apache.bcel.classfile.CodeException;
37  import org.apache.bcel.classfile.ExceptionTable;
38  import org.apache.bcel.classfile.LineNumber;
39  import org.apache.bcel.classfile.LineNumberTable;
40  import org.apache.bcel.classfile.LocalVariable;
41  import org.apache.bcel.classfile.LocalVariableTable;
42  import org.apache.bcel.classfile.LocalVariableTypeTable;
43  import org.apache.bcel.classfile.Method;
44  import org.apache.bcel.classfile.ParameterAnnotationEntry;
45  import org.apache.bcel.classfile.ParameterAnnotations;
46  import org.apache.bcel.classfile.RuntimeVisibleParameterAnnotations;
47  import org.apache.bcel.classfile.Utility;
48  import org.apache.bcel.util.BCELComparator;
49  import org.apache.commons.lang3.ArrayUtils;
50  import org.apache.commons.lang3.stream.Streams;
51  
52  /**
53   * Template class for building up a method. This is done by defining exception handlers, adding thrown exceptions, local
54   * variables and attributes, whereas the 'LocalVariableTable' and 'LineNumberTable' attributes will be set automatically
55   * for the code. Use stripAttributes() if you don't like this.
56   *
57   * While generating code it may be necessary to insert NOP operations. You can use the 'removeNOPs' method to get rid
58   * off them. The resulting method object can be obtained via the 'getMethod()' method.
59   *
60   * @see InstructionList
61   * @see Method
62   */
63  public class MethodGen extends FieldGenOrMethodGen {
64  
65      static final class BranchStack {
66  
67          private final Stack<BranchTarget> branchTargets = new Stack<>();
68          private final Hashtable<InstructionHandle, BranchTarget> visitedTargets = new Hashtable<>();
69  
70          public BranchTarget pop() {
71              if (!branchTargets.empty()) {
72                  return branchTargets.pop();
73              }
74              return null;
75          }
76  
77          public void push(final InstructionHandle target, final int stackDepth) {
78              if (visited(target)) {
79                  return;
80              }
81              branchTargets.push(visit(target, stackDepth));
82          }
83  
84          private BranchTarget visit(final InstructionHandle target, final int stackDepth) {
85              final BranchTarget bt = new BranchTarget(target, stackDepth);
86              visitedTargets.put(target, bt);
87              return bt;
88          }
89  
90          private boolean visited(final InstructionHandle target) {
91              return visitedTargets.get(target) != null;
92          }
93      }
94  
95      static final class BranchTarget {
96  
97          final InstructionHandle target;
98          final int stackDepth;
99  
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