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