001    /*
002     * Copyright 2010-2015 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.kotlin.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.annotations.Nullable;
023    import org.jetbrains.kotlin.codegen.ClosureCodegen;
024    import org.jetbrains.kotlin.codegen.StackValue;
025    import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
026    import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer;
027    import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
028    import org.jetbrains.org.objectweb.asm.Label;
029    import org.jetbrains.org.objectweb.asm.MethodVisitor;
030    import org.jetbrains.org.objectweb.asm.Opcodes;
031    import org.jetbrains.org.objectweb.asm.Type;
032    import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
033    import org.jetbrains.org.objectweb.asm.commons.Method;
034    import org.jetbrains.org.objectweb.asm.commons.RemappingMethodAdapter;
035    import org.jetbrains.org.objectweb.asm.tree.*;
036    import org.jetbrains.org.objectweb.asm.tree.analysis.*;
037    
038    import java.util.*;
039    
040    import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.*;
041    
042    public class MethodInliner {
043    
044        private final MethodNode node;
045    
046        private final Parameters parameters;
047    
048        private final InliningContext inliningContext;
049    
050        private final FieldRemapper nodeRemapper;
051    
052        private final boolean isSameModule;
053    
054        private final String errorPrefix;
055    
056        private final SourceMapper sourceMapper;
057    
058        private final JetTypeMapper typeMapper;
059    
060        private final List<InvokeCall> invokeCalls = new ArrayList<InvokeCall>();
061    
062        //keeps order
063        private final List<AnonymousObjectGeneration> anonymousObjectGenerations = new ArrayList<AnonymousObjectGeneration>();
064        //current state
065        private final Map<String, String> currentTypeMapping = new HashMap<String, String>();
066    
067        private final InlineResult result;
068    
069        private int lambdasFinallyBlocks;
070    
071        /*
072         *
073         * @param node
074         * @param parameters
075         * @param inliningContext
076         * @param lambdaType - in case on lambda 'invoke' inlining
077         */
078        public MethodInliner(
079                @NotNull MethodNode node,
080                @NotNull Parameters parameters,
081                @NotNull InliningContext inliningContext,
082                @NotNull FieldRemapper nodeRemapper,
083                boolean isSameModule,
084                @NotNull String errorPrefix,
085                @NotNull SourceMapper sourceMapper
086        ) {
087            this.node = node;
088            this.parameters = parameters;
089            this.inliningContext = inliningContext;
090            this.nodeRemapper = nodeRemapper;
091            this.isSameModule = isSameModule;
092            this.errorPrefix = errorPrefix;
093            this.sourceMapper = sourceMapper;
094            this.typeMapper = inliningContext.state.getTypeMapper();
095            this.result = InlineResult.create();
096        }
097    
098        public InlineResult doInline(
099                @NotNull MethodVisitor adapter,
100                @NotNull LocalVarRemapper remapper,
101                boolean remapReturn,
102                @NotNull LabelOwner labelOwner
103        ) {
104            return doInline(adapter, remapper, remapReturn, labelOwner, 0);
105        }
106    
107        private InlineResult doInline(
108                @NotNull MethodVisitor adapter,
109                @NotNull LocalVarRemapper remapper,
110                boolean remapReturn,
111                @NotNull LabelOwner labelOwner,
112                int finallyDeepShift
113        ) {
114            //analyze body
115            MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node, finallyDeepShift);
116    
117            //substitute returns with "goto end" instruction to keep non local returns in lambdas
118            Label end = new Label();
119            transformedNode = doInline(transformedNode);
120            removeClosureAssertions(transformedNode);
121            InsnList instructions = transformedNode.instructions;
122            instructions.resetLabels();
123    
124            MethodNode resultNode = new MethodNode(InlineCodegenUtil.API, transformedNode.access, transformedNode.name, transformedNode.desc,
125                                             transformedNode.signature, ArrayUtil.toStringArray(transformedNode.exceptions));
126            RemapVisitor visitor = new RemapVisitor(resultNode, remapper, nodeRemapper);
127            try {
128                transformedNode.accept(visitor);
129            }
130            catch (Exception e) {
131                throw wrapException(e, transformedNode, "couldn't inline method call");
132            }
133    
134            resultNode.visitLabel(end);
135    
136            if (inliningContext.isRoot()) {
137                InternalFinallyBlockInliner.processInlineFunFinallyBlocks(resultNode, lambdasFinallyBlocks, ((StackValue.Local)remapper.remap(parameters.getArgsSizeOnStack() + 1).value).index);
138            }
139    
140            processReturns(resultNode, labelOwner, remapReturn, end);
141            //flush transformed node to output
142            resultNode.accept(new InliningInstructionAdapter(adapter));
143    
144            sourceMapper.endMapping();
145            return result;
146        }
147    
148        private MethodNode doInline(MethodNode node) {
149    
150            final Deque<InvokeCall> currentInvokes = new LinkedList<InvokeCall>(invokeCalls);
151    
152            final MethodNode resultNode = new MethodNode(node.access, node.name, node.desc, node.signature, null);
153    
154            final Iterator<AnonymousObjectGeneration> iterator = anonymousObjectGenerations.iterator();
155    
156            final TypeRemapper remapper = TypeRemapper.createFrom(currentTypeMapping);
157            RemappingMethodAdapter remappingMethodAdapter = new RemappingMethodAdapter(resultNode.access, resultNode.desc, resultNode,
158                                                                                       remapper);
159    
160            final int markerShift = InlineCodegenUtil.calcMarkerShift(parameters, node);
161            InlineAdapter lambdaInliner = new InlineAdapter(remappingMethodAdapter, parameters.getArgsSizeOnStack(), sourceMapper) {
162    
163                private AnonymousObjectGeneration anonymousObjectGen;
164                private void handleAnonymousObjectGeneration() {
165                    anonymousObjectGen = iterator.next();
166    
167                    if (anonymousObjectGen.shouldRegenerate()) {
168                        //TODO: need poping of type but what to do with local funs???
169                        String oldClassName = anonymousObjectGen.getOwnerInternalName();
170                        String newClassName = inliningContext.nameGenerator.genLambdaClassName();
171                        remapper.addMapping(oldClassName, newClassName);
172                        AnonymousObjectTransformer transformer =
173                                new AnonymousObjectTransformer(oldClassName,
174                                                               inliningContext
175                                                                       .subInlineWithClassRegeneration(
176                                                                               inliningContext.nameGenerator,
177                                                                               currentTypeMapping,
178                                                                               anonymousObjectGen),
179                                                               isSameModule, Type.getObjectType(newClassName)
180                                );
181    
182                        InlineResult transformResult = transformer.doTransform(anonymousObjectGen, nodeRemapper);
183                        result.addAllClassesToRemove(transformResult);
184                        result.addChangedType(oldClassName, newClassName);
185    
186                        if (inliningContext.isInliningLambda && !anonymousObjectGen.isStaticOrigin()) {
187                            // this class is transformed and original not used so we should remove original one after inlining
188                            // Note: It is unsafe to remove anonymous class that is referenced by GETSTATIC within lambda
189                            // because it can be local function from outer scope
190                            result.addClassToRemove(oldClassName);
191                        }
192    
193                        if (transformResult.getReifiedTypeParametersUsages().wereUsedReifiedParameters()) {
194                            ReifiedTypeInliner.putNeedClassReificationMarker(mv);
195                            result.getReifiedTypeParametersUsages().mergeAll(transformResult.getReifiedTypeParametersUsages());
196                        }
197                    }
198                }
199    
200                @Override
201                public void anew(@NotNull Type type) {
202                    if (isAnonymousConstructorCall(type.getInternalName(), "<init>")) {
203                        handleAnonymousObjectGeneration();
204                    }
205    
206                    //in case of regenerated anonymousObjectGen type would be remapped to new one via remappingMethodAdapter
207                    super.anew(type);
208                }
209    
210                @Override
211                public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
212                    if (/*INLINE_RUNTIME.equals(owner) &&*/ isInvokeOnLambda(owner, name)) { //TODO add method
213                        assert !currentInvokes.isEmpty();
214                        InvokeCall invokeCall = currentInvokes.remove();
215                        LambdaInfo info = invokeCall.lambdaInfo;
216    
217                        if (info == null) {
218                            //noninlinable lambda
219                            super.visitMethodInsn(opcode, owner, name, desc, itf);
220                            return;
221                        }
222    
223                        int valueParamShift = Math.max(getNextLocalIndex(), markerShift);//NB: don't inline cause it changes
224                        putStackValuesIntoLocals(info.getInvokeParamsWithoutCaptured(), valueParamShift, this, desc);
225    
226                        addInlineMarker(this, true);
227                        Parameters lambdaParameters = info.addAllParameters(nodeRemapper);
228    
229                        InlinedLambdaRemapper newCapturedRemapper =
230                                new InlinedLambdaRemapper(info.getLambdaClassType().getInternalName(), nodeRemapper, lambdaParameters);
231    
232                        setLambdaInlining(true);
233                        SMAP lambdaSMAP = info.getNode().getClassSMAP();
234                        SourceMapper mapper =
235                                inliningContext.classRegeneration && !inliningContext.isInliningLambda ?
236                                new NestedSourceMapper(sourceMapper, lambdaSMAP.getIntervals(), lambdaSMAP.getSourceInfo())
237                                : new InlineLambdaSourceMapper(sourceMapper.getParent(), info.getNode());
238                        MethodInliner inliner = new MethodInliner(info.getNode().getNode(), lambdaParameters,
239                                                                  inliningContext.subInlineLambda(info),
240                                                                  newCapturedRemapper, true /*cause all calls in same module as lambda*/,
241                                                                  "Lambda inlining " + info.getLambdaClassType().getInternalName(),
242                                                                  mapper);
243    
244                        LocalVarRemapper remapper = new LocalVarRemapper(lambdaParameters, valueParamShift);
245                        InlineResult lambdaResult = inliner.doInline(this.mv, remapper, true, info, invokeCall.finallyDepthShift);//TODO add skipped this and receiver
246                        result.addAllClassesToRemove(lambdaResult);
247    
248                        //return value boxing/unboxing
249                        Method bridge =
250                                typeMapper.mapSignature(ClosureCodegen.getErasedInvokeFunction(info.getFunctionDescriptor())).getAsmMethod();
251                        Method delegate = typeMapper.mapSignature(info.getFunctionDescriptor()).getAsmMethod();
252                        StackValue.onStack(delegate.getReturnType()).put(bridge.getReturnType(), this);
253                        setLambdaInlining(false);
254                        addInlineMarker(this, false);
255                        mapper.endMapping();
256                    }
257                    else if (isAnonymousConstructorCall(owner, name)) { //TODO add method
258                        assert anonymousObjectGen != null : "<init> call not corresponds to new call" + owner + " " + name;
259                        if (anonymousObjectGen.shouldRegenerate()) {
260                            //put additional captured parameters on stack
261                            for (CapturedParamDesc capturedParamDesc : anonymousObjectGen.getAllRecapturedParameters()) {
262                                visitFieldInsn(Opcodes.GETSTATIC, capturedParamDesc.getContainingLambdaName(),
263                                               "$$$" + capturedParamDesc.getFieldName(), capturedParamDesc.getType().getDescriptor());
264                            }
265                            String newInternalName = anonymousObjectGen.getNewLambdaType().getInternalName();
266                            super.visitMethodInsn(opcode, newInternalName, name, anonymousObjectGen.getNewConstructorDescriptor(), itf);
267    
268                            //TODO: add new inner class also for other contexts
269                            if (inliningContext.getParent() instanceof RegeneratedClassContext) {
270                                inliningContext.getParent().typeRemapper.addAdditionalMappings(anonymousObjectGen.getOwnerInternalName(), newInternalName);
271                            }
272    
273                            anonymousObjectGen = null;
274                        } else {
275                            super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc, itf);
276                        }
277                    }
278                    else if (ReifiedTypeInliner.isNeedClassReificationMarker(new MethodInsnNode(opcode, owner, name, desc, false))) {
279                        // we will put it if needed in anew processing
280                    }
281                    else {
282                        super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc, itf);
283                    }
284                }
285    
286                @Override
287                public void visitFieldInsn(int opcode, @NotNull String owner, @NotNull String name, @NotNull String desc) {
288                    if (opcode == Opcodes.GETSTATIC && isAnonymousSingletonLoad(owner, name)) {
289                        handleAnonymousObjectGeneration();
290                    }
291                    super.visitFieldInsn(opcode, owner, name, desc);
292                }
293    
294                @Override
295                public void visitMaxs(int stack, int locals) {
296                    lambdasFinallyBlocks = resultNode.tryCatchBlocks.size();
297                    super.visitMaxs(stack, locals);
298                }
299    
300            };
301    
302            node.accept(lambdaInliner);
303    
304            return resultNode;
305        }
306    
307        @NotNull
308        public static CapturedParamInfo findCapturedField(FieldInsnNode node, FieldRemapper fieldRemapper) {
309            assert node.name.startsWith("$$$") : "Captured field template should start with $$$ prefix";
310            FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc);
311            CapturedParamInfo field = fieldRemapper.findField(fin);
312            if (field == null) {
313                throw new IllegalStateException("Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.getLambdaInternalName());
314            }
315            return field;
316        }
317    
318        @NotNull
319        public MethodNode prepareNode(@NotNull MethodNode node, int finallyDeepShift) {
320            final int capturedParamsSize = parameters.getCapturedArgsSizeOnStack();
321            final int realParametersSize = parameters.getRealArgsSizeOnStack();
322            Type[] types = Type.getArgumentTypes(node.desc);
323            Type returnType = Type.getReturnType(node.desc);
324    
325            List<Type> capturedTypes = parameters.getCapturedTypes();
326            Type[] allTypes = ArrayUtil.mergeArrays(types, capturedTypes.toArray(new Type[capturedTypes.size()]));
327    
328            node.instructions.resetLabels();
329            MethodNode transformedNode = new MethodNode(InlineCodegenUtil.API, node.access, node.name, Type.getMethodDescriptor(returnType, allTypes), node.signature, null) {
330    
331                private final boolean isInliningLambda = nodeRemapper.isInsideInliningLambda();
332    
333                private int getNewIndex(int var) {
334                    return var + (var < realParametersSize ? 0 : capturedParamsSize);
335                }
336    
337                @Override
338                public void visitVarInsn(int opcode, int var) {
339                    super.visitVarInsn(opcode, getNewIndex(var));
340                }
341    
342                @Override
343                public void visitIincInsn(int var, int increment) {
344                    super.visitIincInsn(getNewIndex(var), increment);
345                }
346    
347                @Override
348                public void visitMaxs(int maxStack, int maxLocals) {
349                    super.visitMaxs(maxStack, maxLocals + capturedParamsSize);
350                }
351    
352                @Override
353                public void visitLineNumber(int line, @NotNull Label start) {
354                    if(isInliningLambda || InlineCodegenUtil.GENERATE_SMAP) {
355                        super.visitLineNumber(line, start);
356                    }
357                }
358    
359                @Override
360                public void visitLocalVariable(
361                        @NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index
362                ) {
363                    if (isInliningLambda || InlineCodegenUtil.GENERATE_SMAP) {
364                        String varSuffix = inliningContext.isRoot() && !InlineCodegenUtil.isFakeLocalVariableForInline(name) ? INLINE_FUN_VAR_SUFFIX : "";
365                        String varName = !varSuffix.isEmpty() && name.equals("this") ? name + "_" : name;
366                        super.visitLocalVariable(varName + varSuffix, desc, signature, start, end, getNewIndex(index));
367                    }
368                }
369            };
370    
371            node.accept(transformedNode);
372    
373            transformCaptured(transformedNode);
374            transformFinallyDeepIndex(transformedNode, finallyDeepShift);
375    
376            return transformedNode;
377        }
378    
379        @NotNull
380        protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, int finallyDeepShift) {
381            node = prepareNode(node, finallyDeepShift);
382    
383            try {
384                new MandatoryMethodTransformer().transform("fake", node);
385            }
386            catch (Throwable e) {
387                throw wrapException(e, node, "couldn't inline method call");
388            }
389    
390            Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
391                @NotNull
392                @Override
393                protected Frame<SourceValue> newFrame(
394                        int nLocals, int nStack
395                ) {
396                    return new Frame<SourceValue>(nLocals, nStack) {
397                        @Override
398                        public void execute(
399                                @NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter
400                        ) throws AnalyzerException {
401                            if (insn.getOpcode() == Opcodes.RETURN) {
402                                //there is exception on void non local return in frame
403                                return;
404                            }
405                            super.execute(insn, interpreter);
406                        }
407                    };
408                }
409            };
410    
411            Frame<SourceValue>[] sources;
412            try {
413                sources = analyzer.analyze("fake", node);
414            }
415            catch (AnalyzerException e) {
416                throw wrapException(e, node, "couldn't inline method call");
417            }
418    
419            AbstractInsnNode cur = node.instructions.getFirst();
420            int index = 0;
421    
422            boolean awaitClassReification = false;
423            int currentFinallyDeep = 0;
424    
425            while (cur != null) {
426                Frame<SourceValue> frame = sources[index];
427    
428                if (frame != null) {
429                    if (ReifiedTypeInliner.isNeedClassReificationMarker(cur)) {
430                        awaitClassReification = true;
431                    }
432                    else if (cur.getType() == AbstractInsnNode.METHOD_INSN) {
433                        if (InlineCodegenUtil.isFinallyStart(cur)) {
434                            //TODO deep index calc could be more precise
435                            currentFinallyDeep = InlineCodegenUtil.getConstant(cur.getPrevious());
436                        }
437    
438                        MethodInsnNode methodInsnNode = (MethodInsnNode) cur;
439                        String owner = methodInsnNode.owner;
440                        String desc = methodInsnNode.desc;
441                        String name = methodInsnNode.name;
442                        //TODO check closure
443                        Type[] argTypes = Type.getArgumentTypes(desc);
444                        int paramCount = argTypes.length + 1;//non static
445                        int firstParameterIndex = frame.getStackSize() - paramCount;
446                        if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) {
447                            SourceValue sourceValue = frame.getStack(firstParameterIndex);
448    
449                            LambdaInfo lambdaInfo = null;
450                            int varIndex = -1;
451    
452                            if (sourceValue.insns.size() == 1) {
453                                AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
454                                AbstractInsnNode processingInstruction = insnNode;
455    
456                                if (insnNode.getOpcode() == Opcodes.SWAP) {
457                                    processingInstruction = InlineCodegenUtil.getPrevMeaningful(insnNode);
458                                }
459                                lambdaInfo = getLambdaIfExists(processingInstruction);
460                                if (lambdaInfo != null) {
461                                    //remove inlinable access
462                                    assert processingInstruction != null;
463                                    InlineCodegenUtil.removeInterval(node, processingInstruction, insnNode);
464                                }
465                            }
466    
467                            invokeCalls.add(new InvokeCall(varIndex, lambdaInfo, currentFinallyDeep));
468                        }
469                        else if (isAnonymousConstructorCall(owner, name)) {
470                            Map<Integer, LambdaInfo> lambdaMapping = new HashMap<Integer, LambdaInfo>();
471    
472                            int offset = 0;
473                            for (int i = 0; i < paramCount; i++) {
474                                SourceValue sourceValue = frame.getStack(firstParameterIndex + i);
475                                if (sourceValue.insns.size() == 1) {
476                                    AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
477                                    LambdaInfo lambdaInfo = getLambdaIfExists(insnNode);
478                                    if (lambdaInfo != null) {
479                                        lambdaMapping.put(offset, lambdaInfo);
480                                        node.instructions.remove(insnNode);
481                                    }
482                                }
483                                offset += i == 0 ? 1 : argTypes[i - 1].getSize();
484                            }
485    
486                            anonymousObjectGenerations.add(
487                                    buildConstructorInvocation(
488                                            owner, desc, lambdaMapping, awaitClassReification
489                                    )
490                            );
491                            awaitClassReification = false;
492                        }
493                    }
494                    else if (cur.getOpcode() == Opcodes.GETSTATIC) {
495                        FieldInsnNode fieldInsnNode = (FieldInsnNode) cur;
496                        String owner = fieldInsnNode.owner;
497                        if (isAnonymousSingletonLoad(owner, fieldInsnNode.name)) {
498                            anonymousObjectGenerations.add(
499                                    new AnonymousObjectGeneration(
500                                            owner, isSameModule, awaitClassReification, isAlreadyRegenerated(owner), true
501                                    )
502                            );
503                            awaitClassReification = false;
504                        }
505                    }
506                }
507                AbstractInsnNode prevNode = cur;
508                cur = cur.getNext();
509                index++;
510    
511                //given frame is <tt>null</tt> if and only if the corresponding instruction cannot be reached (dead code).
512                if (frame == null) {
513                    //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems
514                    if (prevNode.getType() == AbstractInsnNode.LABEL) {
515                        //NB: Cause we generate exception table for default handler using gaps (see ExpressionCodegen.visitTryExpression)
516                        //it may occurs that interval for default handler starts before catch start label, so this label seems as dead,
517                        //but as result all this labels will be merged into one (see KT-5863)
518                    } else {
519                        node.instructions.remove(prevNode);
520                    }
521                }
522            }
523    
524            //clean dead try/catch blocks
525            List<TryCatchBlockNode> blocks = node.tryCatchBlocks;
526            for (Iterator<TryCatchBlockNode> iterator = blocks.iterator(); iterator.hasNext(); ) {
527                TryCatchBlockNode block = iterator.next();
528                if (isEmptyTryInterval(block)) {
529                    iterator.remove();
530                }
531            }
532    
533            return node;
534        }
535    
536        private static boolean isEmptyTryInterval(@NotNull TryCatchBlockNode tryCatchBlockNode) {
537            LabelNode start = tryCatchBlockNode.start;
538            AbstractInsnNode end = tryCatchBlockNode.end;
539            while (end != start && end instanceof LabelNode) {
540                end = end.getPrevious();
541            }
542            return start == end;
543        }
544    
545        @NotNull
546        private AnonymousObjectGeneration buildConstructorInvocation(
547                @NotNull String owner,
548                @NotNull String desc,
549                @NotNull Map<Integer, LambdaInfo> lambdaMapping,
550                boolean needReification
551        ) {
552            return new AnonymousObjectGeneration(
553                    owner, needReification, isSameModule, lambdaMapping,
554                    inliningContext.classRegeneration,
555                    isAlreadyRegenerated(owner),
556                    desc,
557                    false
558            );
559        }
560    
561        private boolean isAlreadyRegenerated(@NotNull String owner) {
562            return inliningContext.typeRemapper.hasNoAdditionalMapping(owner);
563        }
564    
565        @Nullable
566        public LambdaInfo getLambdaIfExists(@Nullable AbstractInsnNode insnNode) {
567            if (insnNode == null) {
568                return null;
569            }
570    
571            if (insnNode.getOpcode() == Opcodes.ALOAD) {
572                int varIndex = ((VarInsnNode) insnNode).var;
573                return getLambdaIfExists(varIndex);
574            }
575            else if (insnNode instanceof FieldInsnNode) {
576                FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode;
577                if (fieldInsnNode.name.startsWith("$$$")) {
578                    return findCapturedField(fieldInsnNode, nodeRemapper).getLambda();
579                }
580            }
581    
582            return null;
583        }
584    
585        private LambdaInfo getLambdaIfExists(int varIndex) {
586            if (varIndex < parameters.getArgsSizeOnStack()) {
587                return parameters.getParameterByDeclarationSlot(varIndex).getLambda();
588            }
589            return null;
590        }
591    
592        private static void removeClosureAssertions(MethodNode node) {
593            AbstractInsnNode cur = node.instructions.getFirst();
594            while (cur != null && cur.getNext() != null) {
595                AbstractInsnNode next = cur.getNext();
596                if (next.getType() == AbstractInsnNode.METHOD_INSN) {
597                    MethodInsnNode methodInsnNode = (MethodInsnNode) next;
598                    if (methodInsnNode.name.equals("checkParameterIsNotNull") && methodInsnNode.owner.equals(IntrinsicMethods.INTRINSICS_CLASS_NAME)) {
599                        AbstractInsnNode prev = cur.getPrevious();
600    
601                        assert cur.getOpcode() == Opcodes.LDC : "checkParameterIsNotNull should go after LDC but " + cur;
602                        assert prev.getOpcode() == Opcodes.ALOAD : "checkParameterIsNotNull should be invoked on local var but " + prev;
603    
604                        node.instructions.remove(prev);
605                        node.instructions.remove(cur);
606                        cur = next.getNext();
607                        node.instructions.remove(next);
608                        next = cur;
609                    }
610                }
611                cur = next;
612            }
613        }
614    
615        private void transformCaptured(@NotNull MethodNode node) {
616            if (nodeRemapper.isRoot()) {
617                return;
618            }
619    
620            //Fold all captured variable chain - ALOAD 0 ALOAD this$0 GETFIELD $captured - to GETFIELD $$$$captured
621            //On future decoding this field could be inline or unfolded in another field access chain (it can differ in some missed this$0)
622            AbstractInsnNode cur = node.instructions.getFirst();
623            while (cur != null) {
624                if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) {
625                    int varIndex = ((VarInsnNode) cur).var;
626                    if (varIndex == 0 || nodeRemapper.processNonAload0FieldAccessChains(getLambdaIfExists(varIndex) != null)) {
627                        List<AbstractInsnNode> accessChain = getCapturedFieldAccessChain((VarInsnNode) cur);
628                        AbstractInsnNode insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node);
629                        if (insnNode != null) {
630                            cur = insnNode;
631                        }
632                    }
633                }
634                cur = cur.getNext();
635            }
636        }
637    
638        private void transformFinallyDeepIndex(@NotNull MethodNode node, int finallyDeepShift) {
639            if (finallyDeepShift == 0) {
640                return;
641            }
642    
643            AbstractInsnNode cur = node.instructions.getFirst();
644            while (cur != null) {
645                if (cur instanceof MethodInsnNode && InlineCodegenUtil.isFinallyMarker(cur)) {
646                    AbstractInsnNode constant = cur.getPrevious();
647                    int curDeep = InlineCodegenUtil.getConstant(constant);
648                    node.instructions.insert(constant, new LdcInsnNode(curDeep + finallyDeepShift));
649                    node.instructions.remove(constant);
650                }
651                cur = cur.getNext();
652            }
653        }
654    
655        @NotNull
656        public static List<AbstractInsnNode> getCapturedFieldAccessChain(@NotNull VarInsnNode aload0) {
657            List<AbstractInsnNode> fieldAccessChain = new ArrayList<AbstractInsnNode>();
658            fieldAccessChain.add(aload0);
659            AbstractInsnNode next = aload0.getNext();
660            while (next != null && next instanceof FieldInsnNode || next instanceof LabelNode) {
661                if (next instanceof LabelNode) {
662                    next = next.getNext();
663                    continue; //it will be delete on transformation
664                }
665                fieldAccessChain.add(next);
666                if ("this$0".equals(((FieldInsnNode) next).name)) {
667                    next = next.getNext();
668                }
669                else {
670                    break;
671                }
672            }
673    
674            return fieldAccessChain;
675        }
676    
677        public static void putStackValuesIntoLocals(List<Type> directOrder, int shift, InstructionAdapter iv, String descriptor) {
678            Type[] actualParams = Type.getArgumentTypes(descriptor);
679            assert actualParams.length == directOrder.size() : "Number of expected and actual params should be equals!";
680    
681            int size = 0;
682            for (Type next : directOrder) {
683                size += next.getSize();
684            }
685    
686            shift += size;
687            int index = directOrder.size();
688    
689            for (Type next : Lists.reverse(directOrder)) {
690                shift -= next.getSize();
691                Type typeOnStack = actualParams[--index];
692                if (!typeOnStack.equals(next)) {
693                    StackValue.onStack(typeOnStack).put(next, iv);
694                }
695                iv.store(shift, next);
696            }
697        }
698    
699        //TODO: check it's external module
700        //TODO?: assert method exists in facade?
701        public String changeOwnerForExternalPackage(String type, int opcode) {
702            //if (isSameModule || (opcode & Opcodes.INVOKESTATIC) == 0) {
703            //    return type;
704            //}
705    
706            //JvmClassName name = JvmClassName.byInternalName(type);
707            //String packageClassInternalName = PackageClassUtils.getPackageClassInternalName(name.getPackageFqName());
708            //if (type.startsWith(packageClassInternalName + '$')) {
709            //    VirtualFile virtualFile = InlineCodegenUtil.findVirtualFile(inliningContext.state.getProject(), type);
710            //    if (virtualFile != null) {
711            //        KotlinJvmBinaryClass klass = KotlinBinaryClassCache.getKotlinBinaryClass(virtualFile);
712            //        if (klass != null && klass.getClassHeader().getSyntheticClassKind() == KotlinSyntheticClass.Kind.PACKAGE_PART) {
713            //            return packageClassInternalName;
714            //        }
715            //    }
716            //}
717    
718            return type;
719        }
720    
721        @NotNull
722        public RuntimeException wrapException(@NotNull Throwable originalException, @NotNull MethodNode node, @NotNull String errorSuffix) {
723            if (originalException instanceof InlineException) {
724                return new InlineException(errorPrefix + ": " + errorSuffix, originalException);
725            }
726            else {
727                return new InlineException(errorPrefix + ": " + errorSuffix + "\ncause: " +
728                                           getNodeText(node), originalException);
729            }
730        }
731    
732        @NotNull
733        //process local and global returns (local substituted with goto end-label global kept unchanged)
734        public static List<PointForExternalFinallyBlocks> processReturns(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, boolean remapReturn, Label endLabel) {
735            if (!remapReturn) {
736                return Collections.emptyList();
737            }
738            List<PointForExternalFinallyBlocks> result = new ArrayList<PointForExternalFinallyBlocks>();
739            InsnList instructions = node.instructions;
740            AbstractInsnNode insnNode = instructions.getFirst();
741            while (insnNode != null) {
742                if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) {
743                    AbstractInsnNode previous = insnNode.getPrevious();
744                    MethodInsnNode flagNode;
745                    boolean isLocalReturn = true;
746                    String labelName = null;
747                    if (previous != null && previous instanceof MethodInsnNode && InlineCodegenUtil.NON_LOCAL_RETURN.equals(((MethodInsnNode) previous).owner)) {
748                        flagNode = (MethodInsnNode) previous;
749                        labelName = flagNode.name;
750                    }
751    
752                    if (labelName != null) {
753                        isLocalReturn = labelOwner.isMyLabel(labelName);
754                        //remove global return flag
755                        if (isLocalReturn) {
756                            instructions.remove(previous);
757                        }
758                    }
759    
760                    if (isLocalReturn && endLabel != null) {
761                        LabelNode labelNode = (LabelNode) endLabel.info;
762                        JumpInsnNode jumpInsnNode = new JumpInsnNode(Opcodes.GOTO, labelNode);
763                        instructions.insert(insnNode, jumpInsnNode);
764                        instructions.remove(insnNode);
765                        insnNode = jumpInsnNode;
766                    }
767    
768                    //genetate finally block before nonLocalReturn flag/return/goto
769                    LabelNode label = new LabelNode();
770                    instructions.insert(insnNode, label);
771                    result.add(new PointForExternalFinallyBlocks(getInstructionToInsertFinallyBefore(insnNode, isLocalReturn),
772                                                                 getReturnType(insnNode.getOpcode()),
773                                                                 label));
774                }
775                insnNode = insnNode.getNext();
776            }
777            return result;
778        }
779    
780        @NotNull
781        private static AbstractInsnNode getInstructionToInsertFinallyBefore(@NotNull AbstractInsnNode nonLocalReturnOrJump, boolean isLocal)  {
782            return isLocal ? nonLocalReturnOrJump : nonLocalReturnOrJump.getPrevious();
783        }
784    
785        //Place to insert finally blocks from try blocks that wraps inline fun call
786        public static class PointForExternalFinallyBlocks {
787    
788            final AbstractInsnNode beforeIns;
789    
790            final Type returnType;
791    
792            final LabelNode finallyIntervalEnd;
793    
794            public PointForExternalFinallyBlocks(
795                    @NotNull AbstractInsnNode beforeIns,
796                    @NotNull Type returnType,
797                    @NotNull LabelNode finallyIntervalEnd
798            ) {
799                this.beforeIns = beforeIns;
800                this.returnType = returnType;
801                this.finallyIntervalEnd = finallyIntervalEnd;
802            }
803    
804        }
805    
806    }