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