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