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