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