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