001    /*
002     * Copyright 2010-2014 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.jet.codegen.inline;
018    
019    import com.google.common.collect.Lists;
020    import com.intellij.util.ArrayUtil;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.jet.codegen.ClosureCodegen;
023    import org.jetbrains.jet.codegen.StackValue;
024    import org.jetbrains.jet.codegen.state.JetTypeMapper;
025    import org.jetbrains.org.objectweb.asm.Label;
026    import org.jetbrains.org.objectweb.asm.MethodVisitor;
027    import org.jetbrains.org.objectweb.asm.Opcodes;
028    import org.jetbrains.org.objectweb.asm.Type;
029    import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
030    import org.jetbrains.org.objectweb.asm.commons.Method;
031    import org.jetbrains.org.objectweb.asm.commons.RemappingMethodAdapter;
032    import org.jetbrains.org.objectweb.asm.tree.*;
033    import org.jetbrains.org.objectweb.asm.tree.analysis.*;
034    
035    import java.util.*;
036    
037    import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.getReturnType;
038    import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.isAnonymousConstructorCall;
039    import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.isInvokeOnLambda;
040    
041    public class MethodInliner {
042    
043        private final MethodNode node;
044    
045        private final Parameters parameters;
046    
047        private final InliningContext inliningContext;
048    
049        private final FieldRemapper nodeRemapper;
050    
051        private final boolean isSameModule;
052    
053        private final String errorPrefix;
054    
055        private final JetTypeMapper typeMapper;
056    
057        private final List<InvokeCall> invokeCalls = new ArrayList<InvokeCall>();
058    
059        //keeps order
060        private final List<ConstructorInvocation> constructorInvocations = new ArrayList<ConstructorInvocation>();
061        //current state
062        private final Map<String, String> currentTypeMapping = new HashMap<String, String>();
063    
064        private final InlineResult result;
065    
066        /*
067         *
068         * @param node
069         * @param parameters
070         * @param inliningContext
071         * @param lambdaType - in case on lambda 'invoke' inlining
072         */
073        public MethodInliner(
074                @NotNull MethodNode node,
075                @NotNull Parameters parameters,
076                @NotNull InliningContext parent,
077                @NotNull FieldRemapper nodeRemapper,
078                boolean isSameModule,
079                @NotNull String errorPrefix
080        ) {
081            this.node = node;
082            this.parameters = parameters;
083            this.inliningContext = parent;
084            this.nodeRemapper = nodeRemapper;
085            this.isSameModule = isSameModule;
086            this.errorPrefix = errorPrefix;
087            this.typeMapper = parent.state.getTypeMapper();
088            this.result = InlineResult.create();
089        }
090    
091        public InlineResult doInline(
092                @NotNull MethodVisitor adapter,
093                @NotNull LocalVarRemapper remapper,
094                boolean remapReturn,
095                @NotNull LabelOwner labelOwner
096        ) {
097            //analyze body
098            MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node);
099    
100            //substitute returns with "goto end" instruction to keep non local returns in lambdas
101            Label end = new Label();
102            transformedNode = doInline(transformedNode);
103            removeClosureAssertions(transformedNode);
104            InsnList instructions = transformedNode.instructions;
105            instructions.resetLabels();
106    
107            MethodNode resultNode = new MethodNode(InlineCodegenUtil.API, transformedNode.access, transformedNode.name, transformedNode.desc,
108                                             transformedNode.signature, ArrayUtil.toStringArray(transformedNode.exceptions));
109            RemapVisitor visitor = new RemapVisitor(resultNode, remapper, nodeRemapper);
110            try {
111                transformedNode.accept(visitor);
112            }
113            catch (Exception e) {
114                throw wrapException(e, transformedNode, "couldn't inline method call");
115            }
116    
117            resultNode.visitLabel(end);
118            processReturns(resultNode, labelOwner, remapReturn, end);
119    
120            //flush transformed node to output
121            resultNode.accept(new InliningInstructionAdapter(adapter));
122    
123            return result;
124        }
125    
126        private MethodNode doInline(MethodNode node) {
127    
128            final Deque<InvokeCall> currentInvokes = new LinkedList<InvokeCall>(invokeCalls);
129    
130            MethodNode resultNode = new MethodNode(node.access, node.name, node.desc, node.signature, null);
131    
132            final Iterator<ConstructorInvocation> iterator = constructorInvocations.iterator();
133    
134            RemappingMethodAdapter remappingMethodAdapter = new RemappingMethodAdapter(resultNode.access, resultNode.desc, resultNode,
135                                                                                       new TypeRemapper(currentTypeMapping));
136    
137            InlineAdapter lambdaInliner = new InlineAdapter(remappingMethodAdapter, parameters.totalSize()) {
138    
139                private ConstructorInvocation invocation;
140                @Override
141                public void anew(@NotNull Type type) {
142                    if (isAnonymousConstructorCall(type.getInternalName(), "<init>")) {
143                        invocation = iterator.next();
144    
145                        if (invocation.shouldRegenerate()) {
146                            //TODO: need poping of type but what to do with local funs???
147                            Type newLambdaType = Type.getObjectType(inliningContext.nameGenerator.genLambdaClassName());
148                            currentTypeMapping.put(invocation.getOwnerInternalName(), newLambdaType.getInternalName());
149                            AnonymousObjectTransformer transformer =
150                                    new AnonymousObjectTransformer(invocation.getOwnerInternalName(),
151                                                                   inliningContext
152                                                                           .subInlineWithClassRegeneration(
153                                                                                   inliningContext.nameGenerator,
154                                                                                   currentTypeMapping,
155                                                                                   invocation),
156                                                                   isSameModule, newLambdaType
157                                    );
158    
159                            InlineResult transformResult = transformer.doTransform(invocation, nodeRemapper);
160                            result.addAllClassesToRemove(transformResult);
161    
162                            if (inliningContext.isInliningLambda) {
163                                //this class is transformed and original not used so we should remove original one after inlining
164                                result.addClassToRemove(invocation.getOwnerInternalName());
165                            }
166                        }
167                    }
168    
169                    //in case of regenerated invocation type would be remapped to new one via remappingMethodAdapter
170                    super.anew(type);
171                }
172    
173                @Override
174                public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
175                    if (/*INLINE_RUNTIME.equals(owner) &&*/ isInvokeOnLambda(owner, name)) { //TODO add method
176                        assert !currentInvokes.isEmpty();
177                        InvokeCall invokeCall = currentInvokes.remove();
178                        LambdaInfo info = invokeCall.lambdaInfo;
179    
180                        if (info == null) {
181                            //noninlinable lambda
182                            super.visitMethodInsn(opcode, owner, name, desc, itf);
183                            return;
184                        }
185    
186                        int valueParamShift = getNextLocalIndex();//NB: don't inline cause it changes
187                        putStackValuesIntoLocals(info.getInvokeParamsWithoutCaptured(), valueParamShift, this, desc);
188    
189                        Parameters lambdaParameters = info.addAllParameters(nodeRemapper);
190    
191                        InlinedLambdaRemapper newCapturedRemapper =
192                                new InlinedLambdaRemapper(info.getLambdaClassType().getInternalName(), nodeRemapper, lambdaParameters);
193    
194                        setLambdaInlining(true);
195                        MethodInliner inliner = new MethodInliner(info.getNode(), lambdaParameters,
196                                                                  inliningContext.subInlineLambda(info),
197                                                                  newCapturedRemapper, true /*cause all calls in same module as lambda*/,
198                                                                  "Lambda inlining " + info.getLambdaClassType().getInternalName());
199    
200                        LocalVarRemapper remapper = new LocalVarRemapper(lambdaParameters, valueParamShift);
201                        InlineResult lambdaResult = inliner.doInline(this.mv, remapper, true, info);//TODO add skipped this and receiver
202                        result.addAllClassesToRemove(lambdaResult);
203    
204                        //return value boxing/unboxing
205                        Method bridge =
206                                typeMapper.mapSignature(ClosureCodegen.getErasedInvokeFunction(info.getFunctionDescriptor())).getAsmMethod();
207                        Method delegate = typeMapper.mapSignature(info.getFunctionDescriptor()).getAsmMethod();
208                        StackValue.onStack(delegate.getReturnType()).put(bridge.getReturnType(), this);
209                        setLambdaInlining(false);
210                    }
211                    else if (isAnonymousConstructorCall(owner, name)) { //TODO add method
212                        assert invocation != null : "<init> call not corresponds to new call" + owner + " " + name;
213                        if (invocation.shouldRegenerate()) {
214                            //put additional captured parameters on stack
215                            for (CapturedParamDesc capturedParamDesc : invocation.getAllRecapturedParameters()) {
216                                visitFieldInsn(Opcodes.GETSTATIC, capturedParamDesc.getContainingLambdaName(),
217                                               "$$$" + capturedParamDesc.getFieldName(), capturedParamDesc.getType().getDescriptor());
218                            }
219                            super.visitMethodInsn(opcode, invocation.getNewLambdaType().getInternalName(), name, invocation.getNewConstructorDescriptor(), itf);
220                            invocation = null;
221                        } else {
222                            super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc, itf);
223                        }
224                    }
225                    else {
226                        super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc, itf);
227                    }
228                }
229    
230            };
231    
232            node.accept(lambdaInliner);
233    
234            return resultNode;
235        }
236    
237        @NotNull
238        public static CapturedParamInfo findCapturedField(FieldInsnNode node, FieldRemapper fieldRemapper) {
239            assert node.name.startsWith("$$$") : "Captured field template should start with $$$ prefix";
240            FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc);
241            CapturedParamInfo field = fieldRemapper.findField(fin);
242            if (field == null) {
243                throw new IllegalStateException("Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.getLambdaInternalName());
244            }
245            return field;
246        }
247    
248        @NotNull
249        public MethodNode prepareNode(@NotNull MethodNode node) {
250            final int capturedParamsSize = parameters.getCaptured().size();
251            final int realParametersSize = parameters.getReal().size();
252            Type[] types = Type.getArgumentTypes(node.desc);
253            Type returnType = Type.getReturnType(node.desc);
254    
255            ArrayList<Type> capturedTypes = parameters.getCapturedTypes();
256            Type[] allTypes = ArrayUtil.mergeArrays(types, capturedTypes.toArray(new Type[capturedTypes.size()]));
257    
258            node.instructions.resetLabels();
259            MethodNode transformedNode = new MethodNode(InlineCodegenUtil.API, node.access, node.name, Type.getMethodDescriptor(returnType, allTypes), node.signature, null) {
260    
261                private final boolean isInliningLambda = nodeRemapper.isInsideInliningLambda();
262    
263                private int getNewIndex(int var) {
264                    return var + (var < realParametersSize ? 0 : capturedParamsSize);
265                }
266    
267                @Override
268                public void visitVarInsn(int opcode, int var) {
269                    super.visitVarInsn(opcode, getNewIndex(var));
270                }
271    
272                @Override
273                public void visitIincInsn(int var, int increment) {
274                    super.visitIincInsn(getNewIndex(var), increment);
275                }
276    
277                @Override
278                public void visitMaxs(int maxStack, int maxLocals) {
279                    super.visitMaxs(maxStack, maxLocals + capturedParamsSize);
280                }
281    
282                @Override
283                public void visitLineNumber(int line, @NotNull Label start) {
284                    if(isInliningLambda) {
285                        super.visitLineNumber(line, start);
286                    }
287                }
288    
289                @Override
290                public void visitLocalVariable(
291                        @NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index
292                ) {
293                    if (isInliningLambda) {
294                        super.visitLocalVariable(name, desc, signature, start, end, getNewIndex(index));
295                    }
296                }
297            };
298    
299            node.accept(transformedNode);
300    
301            transformCaptured(transformedNode);
302    
303            return transformedNode;
304        }
305    
306        @NotNull
307        protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node) {
308            node = prepareNode(node);
309    
310            Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
311                @NotNull
312                @Override
313                protected Frame<SourceValue> newFrame(
314                        int nLocals, int nStack
315                ) {
316                    return new Frame<SourceValue>(nLocals, nStack) {
317                        @Override
318                        public void execute(
319                                @NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter
320                        ) throws AnalyzerException {
321                            if (insn.getOpcode() == Opcodes.RETURN) {
322                                //there is exception on void non local return in frame
323                                return;
324                            }
325                            super.execute(insn, interpreter);
326                        }
327                    };
328                }
329            };
330    
331            Frame<SourceValue>[] sources;
332            try {
333                sources = analyzer.analyze("fake", node);
334            }
335            catch (AnalyzerException e) {
336                throw wrapException(e, node, "couldn't inline method call");
337            }
338    
339            AbstractInsnNode cur = node.instructions.getFirst();
340            int index = 0;
341            Set<LabelNode> deadLabels = new HashSet<LabelNode>();
342    
343            while (cur != null) {
344                Frame<SourceValue> frame = sources[index];
345    
346                if (frame != null) {
347                    if (cur.getType() == AbstractInsnNode.METHOD_INSN) {
348                        MethodInsnNode methodInsnNode = (MethodInsnNode) cur;
349                        String owner = methodInsnNode.owner;
350                        String desc = methodInsnNode.desc;
351                        String name = methodInsnNode.name;
352                        //TODO check closure
353                        int paramLength = Type.getArgumentTypes(desc).length + 1;//non static
354                        if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) {
355                            SourceValue sourceValue = frame.getStack(frame.getStackSize() - paramLength);
356    
357                            LambdaInfo lambdaInfo = null;
358                            int varIndex = -1;
359    
360                            if (sourceValue.insns.size() == 1) {
361                                AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
362    
363                                lambdaInfo = getLambdaIfExists(insnNode);
364                                if (lambdaInfo != null) {
365                                    //remove inlinable access
366                                    node.instructions.remove(insnNode);
367                                }
368                            }
369    
370                            invokeCalls.add(new InvokeCall(varIndex, lambdaInfo));
371                        }
372                        else if (isAnonymousConstructorCall(owner, name)) {
373                            Map<Integer, LambdaInfo> lambdaMapping = new HashMap<Integer, LambdaInfo>();
374                            int paramStart = frame.getStackSize() - paramLength;
375    
376                            for (int i = 0; i < paramLength; i++) {
377                                SourceValue sourceValue = frame.getStack(paramStart + i);
378                                if (sourceValue.insns.size() == 1) {
379                                    AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
380                                    LambdaInfo lambdaInfo = getLambdaIfExists(insnNode);
381                                    if (lambdaInfo != null) {
382                                        lambdaMapping.put(i, lambdaInfo);
383                                        node.instructions.remove(insnNode);
384                                    }
385                                }
386                            }
387    
388                            constructorInvocations.add(new ConstructorInvocation(owner, desc, lambdaMapping, isSameModule, inliningContext.classRegeneration));
389                        }
390                    }
391                }
392    
393                AbstractInsnNode prevNode = cur;
394                cur = cur.getNext();
395                index++;
396    
397                //given frame is <tt>null</tt> if and only if the corresponding instruction cannot be reached (dead code).
398                if (frame == null) {
399                    //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems
400                    if (prevNode.getType() == AbstractInsnNode.LABEL) {
401                        deadLabels.add((LabelNode) prevNode);
402                    } else {
403                        node.instructions.remove(prevNode);
404                    }
405                }
406            }
407    
408            //clean dead try/catch blocks
409            List<TryCatchBlockNode> blocks = node.tryCatchBlocks;
410            for (Iterator<TryCatchBlockNode> iterator = blocks.iterator(); iterator.hasNext(); ) {
411                TryCatchBlockNode block = iterator.next();
412                if (deadLabels.contains(block.start) && deadLabels.contains(block.end)) {
413                    iterator.remove();
414                }
415            }
416    
417            return node;
418        }
419    
420        public LambdaInfo getLambdaIfExists(AbstractInsnNode insnNode) {
421            if (insnNode.getOpcode() == Opcodes.ALOAD) {
422                int varIndex = ((VarInsnNode) insnNode).var;
423                if (varIndex < parameters.totalSize()) {
424                    return parameters.get(varIndex).getLambda();
425                }
426            }
427            else if (insnNode instanceof FieldInsnNode) {
428                FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode;
429                if (fieldInsnNode.name.startsWith("$$$")) {
430                    return findCapturedField(fieldInsnNode, nodeRemapper).getLambda();
431                }
432            }
433    
434            return null;
435        }
436    
437        private static void removeClosureAssertions(MethodNode node) {
438            AbstractInsnNode cur = node.instructions.getFirst();
439            while (cur != null && cur.getNext() != null) {
440                AbstractInsnNode next = cur.getNext();
441                if (next.getType() == AbstractInsnNode.METHOD_INSN) {
442                    MethodInsnNode methodInsnNode = (MethodInsnNode) next;
443                    if (methodInsnNode.name.equals("checkParameterIsNotNull") && methodInsnNode.owner.equals("kotlin/jvm/internal/Intrinsics")) {
444                        AbstractInsnNode prev = cur.getPrevious();
445    
446                        assert cur.getOpcode() == Opcodes.LDC : "checkParameterIsNotNull should go after LDC but " + cur;
447                        assert prev.getOpcode() == Opcodes.ALOAD : "checkParameterIsNotNull should be invoked on local var but " + prev;
448    
449                        node.instructions.remove(prev);
450                        node.instructions.remove(cur);
451                        cur = next.getNext();
452                        node.instructions.remove(next);
453                        next = cur;
454                    }
455                }
456                cur = next;
457            }
458        }
459    
460        private void transformCaptured(@NotNull MethodNode node) {
461            if (nodeRemapper.isRoot()) {
462                return;
463            }
464    
465            //Fold all captured variable chain - ALOAD 0 ALOAD this$0 GETFIELD $captured - to GETFIELD $$$$captured
466            //On future decoding this field could be inline or unfolded in another field access chain (it can differ in some missed this$0)
467            AbstractInsnNode cur = node.instructions.getFirst();
468            while (cur != null) {
469                if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) {
470                    if (((VarInsnNode) cur).var == 0) {
471                        List<AbstractInsnNode> accessChain = getCapturedFieldAccessChain((VarInsnNode) cur);
472                        AbstractInsnNode insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node);
473                        if (insnNode != null) {
474                            cur = insnNode;
475                        }
476                    }
477                }
478                cur = cur.getNext();
479            }
480        }
481    
482        @NotNull
483        public static List<AbstractInsnNode> getCapturedFieldAccessChain(@NotNull VarInsnNode aload0) {
484            List<AbstractInsnNode> fieldAccessChain = new ArrayList<AbstractInsnNode>();
485            fieldAccessChain.add(aload0);
486            AbstractInsnNode next = aload0.getNext();
487            while (next != null && next instanceof FieldInsnNode || next instanceof LabelNode) {
488                if (next instanceof LabelNode) {
489                    next = next.getNext();
490                    continue; //it will be delete on transformation
491                }
492                fieldAccessChain.add(next);
493                if ("this$0".equals(((FieldInsnNode) next).name)) {
494                    next = next.getNext();
495                }
496                else {
497                    break;
498                }
499            }
500    
501            return fieldAccessChain;
502        }
503    
504        public static void putStackValuesIntoLocals(List<Type> directOrder, int shift, InstructionAdapter iv, String descriptor) {
505            Type[] actualParams = Type.getArgumentTypes(descriptor);
506            assert actualParams.length == directOrder.size() : "Number of expected and actual params should be equals!";
507    
508            int size = 0;
509            for (Type next : directOrder) {
510                size += next.getSize();
511            }
512    
513            shift += size;
514            int index = directOrder.size();
515    
516            for (Type next : Lists.reverse(directOrder)) {
517                shift -= next.getSize();
518                Type typeOnStack = actualParams[--index];
519                if (!typeOnStack.equals(next)) {
520                    StackValue.onStack(typeOnStack).put(next, iv);
521                }
522                iv.store(shift, next);
523            }
524        }
525    
526        //TODO: check annotation on class - it's package part
527        //TODO: check it's external module
528        //TODO?: assert method exists in facade?
529        public String changeOwnerForExternalPackage(String type, int opcode) {
530            if (isSameModule || (opcode & Opcodes.INVOKESTATIC) == 0) {
531                return type;
532            }
533    
534            int i = type.indexOf('-');
535            if (i >= 0) {
536                return type.substring(0, i);
537            }
538            return type;
539        }
540    
541        @NotNull
542        public RuntimeException wrapException(@NotNull Exception originalException, @NotNull MethodNode node, @NotNull String errorSuffix) {
543            if (originalException instanceof InlineException) {
544                return new InlineException(errorPrefix + ": " + errorSuffix, originalException);
545            } else {
546                return new InlineException(errorPrefix + ": " + errorSuffix + "\ncause: " +
547                                           InlineCodegen.getNodeText(node), originalException);
548            }
549        }
550    
551        @NotNull
552        public static List<FinallyBlockInfo> processReturns(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, boolean remapReturn, Label endLabel) {
553            if (!remapReturn) {
554                return Collections.emptyList();
555            }
556            List<FinallyBlockInfo> result = new ArrayList<FinallyBlockInfo>();
557            InsnList instructions = node.instructions;
558            AbstractInsnNode insnNode = instructions.getFirst();
559            while (insnNode != null) {
560                if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) {
561                    AbstractInsnNode previous = insnNode.getPrevious();
562                    MethodInsnNode flagNode;
563                    boolean isLocalReturn = true;
564                    String labelName = null;
565                    if (previous != null && previous instanceof MethodInsnNode && InlineCodegenUtil.NON_LOCAL_RETURN.equals(((MethodInsnNode) previous).owner)) {
566                        flagNode = (MethodInsnNode) previous;
567                        labelName = flagNode.name;
568                    }
569    
570                    if (labelName != null) {
571                        isLocalReturn = labelOwner.isMyLabel(labelName);
572                        //remove global return flag
573                        if (isLocalReturn) {
574                            instructions.remove(previous);
575                        }
576                    }
577    
578                    if (isLocalReturn && endLabel != null) {
579                        LabelNode labelNode = (LabelNode) endLabel.info;
580                        JumpInsnNode jumpInsnNode = new JumpInsnNode(Opcodes.GOTO, labelNode);
581                        instructions.insert(insnNode, jumpInsnNode);
582                        instructions.remove(insnNode);
583                        insnNode = jumpInsnNode;
584                    }
585    
586                    //genetate finally block before nonLocalReturn flag/return/goto
587                    result.add(new FinallyBlockInfo(isLocalReturn ? insnNode : insnNode.getPrevious(), getReturnType(insnNode.getOpcode())));
588                }
589                insnNode = insnNode.getNext();
590            }
591            return result;
592        }
593    
594        public static class FinallyBlockInfo {
595    
596            final AbstractInsnNode beforeIns;
597    
598            final Type returnType;
599    
600            public FinallyBlockInfo(AbstractInsnNode beforeIns, Type returnType) {
601                this.beforeIns = beforeIns;
602                this.returnType = returnType;
603            }
604    
605        }
606    }