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