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