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