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