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