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