All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.codegen.inline.MethodInliner.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.kotlin.codegen.inline

import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.ClosureCodegen
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
import org.jetbrains.kotlin.utils.SmartList
import org.jetbrains.kotlin.utils.SmartSet
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.MethodVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.commons.RemappingMethodAdapter
import org.jetbrains.org.objectweb.asm.tree.*
import org.jetbrains.org.objectweb.asm.tree.analysis.*
import org.jetbrains.org.objectweb.asm.util.Printer
import java.util.*

class MethodInliner(
        private val node: MethodNode,
        private val parameters: Parameters,
        private val inliningContext: InliningContext,
        private val nodeRemapper: FieldRemapper,
        private val isSameModule: Boolean,
        private val errorPrefix: String,
        private val sourceMapper: SourceMapper,
        private val inlineCallSiteInfo: InlineCallSiteInfo,
        private val inlineOnlySmapSkipper: InlineOnlySmapSkipper? //non null only for root
) {
    private val typeMapper = inliningContext.state.typeMapper
    private val invokeCalls = ArrayList()
    //keeps order
    private val transformations = ArrayList()
    //current state
    private val currentTypeMapping = HashMap()
    private val result = InlineResult.create()
    private var lambdasFinallyBlocks: Int = 0

    fun doInline(
            adapter: MethodVisitor,
            remapper: LocalVarRemapper,
            remapReturn: Boolean,
            labelOwner: LabelOwner
    ): InlineResult {
        return doInline(adapter, remapper, remapReturn, labelOwner, 0)
    }

    private fun doInline(
            adapter: MethodVisitor,
            remapper: LocalVarRemapper,
            remapReturn: Boolean,
            labelOwner: LabelOwner,
            finallyDeepShift: Int
    ): InlineResult {
        //analyze body
        var transformedNode = markPlacesForInlineAndRemoveInlinable(node, labelOwner, finallyDeepShift)
        if (inliningContext.isInliningLambda && isDefaultLambdaWithReification(inliningContext.lambdaInfo!!)) {
            //TODO maybe move reification in one place
            inliningContext.root.inlineMethodReifier.reifyInstructions(transformedNode)
        }

        //substitute returns with "goto end" instruction to keep non local returns in lambdas
        val end = Label()
        transformedNode = doInline(transformedNode)
        removeClosureAssertions(transformedNode)
        transformedNode.instructions.resetLabels()

        val resultNode = MethodNode(
                API, transformedNode.access, transformedNode.name, transformedNode.desc,
                transformedNode.signature, transformedNode.exceptions?.toTypedArray()
        )
        val visitor = RemapVisitor(resultNode, remapper, nodeRemapper)
        try {
            transformedNode.accept(visitor)
        }
        catch (e: Throwable) {
            throw wrapException(e, transformedNode, "couldn't inline method call")
        }

        resultNode.visitLabel(end)

        if (inliningContext.isRoot) {
            val remapValue = remapper.remap(parameters.argsSizeOnStack + 1).value
            InternalFinallyBlockInliner.processInlineFunFinallyBlocks(
                    resultNode, lambdasFinallyBlocks, (remapValue as StackValue.Local).index
            )
        }

        processReturns(resultNode, labelOwner, remapReturn, end)
        //flush transformed node to output
        resultNode.accept(MethodBodyVisitor(adapter))

        sourceMapper.endMapping()
        return result
    }

    private fun doInline(node: MethodNode): MethodNode {
        val currentInvokes = LinkedList(invokeCalls)

        val resultNode = MethodNode(node.access, node.name, node.desc, node.signature, null)

        val iterator = transformations.iterator()

        val remapper = TypeRemapper.createFrom(currentTypeMapping)
        val remappingMethodAdapter = RemappingMethodAdapter(
                resultNode.access,
                resultNode.desc,
                resultNode,
                AsmTypeRemapper(remapper, result)
        )

        val markerShift = calcMarkerShift(parameters, node)
        val lambdaInliner = object : InlineAdapter(remappingMethodAdapter, parameters.argsSizeOnStack, sourceMapper) {
            private var transformationInfo: TransformationInfo? = null

            private fun handleAnonymousObjectRegeneration() {
                transformationInfo = iterator.next()

                val oldClassName = transformationInfo!!.oldClassName
                if (transformationInfo!!.shouldRegenerate(isSameModule)) {
                    //TODO: need poping of type but what to do with local funs???
                    val newClassName = transformationInfo!!.newClassName
                    remapper.addMapping(oldClassName, newClassName)

                    val childInliningContext = inliningContext.subInlineWithClassRegeneration(
                            inliningContext.nameGenerator,
                            currentTypeMapping,
                            inlineCallSiteInfo
                    )
                    val transformer = transformationInfo!!.createTransformer(childInliningContext, isSameModule)

                    val transformResult = transformer.doTransform(nodeRemapper)
                    result.merge(transformResult)
                    result.addChangedType(oldClassName, newClassName)

                    if (inliningContext.isInliningLambda && transformationInfo!!.canRemoveAfterTransformation()) {
                        // this class is transformed and original not used so we should remove original one after inlining
                        result.addClassToRemove(oldClassName)
                    }

                    if (transformResult.reifiedTypeParametersUsages.wereUsedReifiedParameters()) {
                        ReifiedTypeInliner.putNeedClassReificationMarker(mv)
                        result.reifiedTypeParametersUsages.mergeAll(transformResult.reifiedTypeParametersUsages)
                    }
                }
                else if (!transformationInfo!!.wasAlreadyRegenerated) {
                    result.addNotChangedClass(oldClassName)
                }
            }

            override fun anew(type: Type) {
                if (isAnonymousClass(type.internalName)) {
                    handleAnonymousObjectRegeneration()
                }

                //in case of regenerated transformationInfo type would be remapped to new one via remappingMethodAdapter
                super.anew(type)
            }

            override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
                if (/*INLINE_RUNTIME.equals(owner) &&*/ isInvokeOnLambda(owner, name)) { //TODO add method
                    assert(!currentInvokes.isEmpty())
                    val invokeCall = currentInvokes.remove()
                    val info = invokeCall.lambdaInfo

                    if (info == null) {
                        //noninlinable lambda
                        super.visitMethodInsn(opcode, owner, name, desc, itf)
                        return
                    }

                    val valueParamShift = Math.max(nextLocalIndex, markerShift)//NB: don't inline cause it changes
                    putStackValuesIntoLocals(listOf(*info.invokeMethod.argumentTypes), valueParamShift, this, desc)

                    if (invokeCall.lambdaInfo.invokeMethodDescriptor.valueParameters.isEmpty()) {
                        // There won't be no parameters processing and line call can be left without actual instructions.
                        // Note: if function is called on the line with other instructions like 1 + foo(), 'nop' will still be generated.
                        visitInsn(Opcodes.NOP)
                    }

                    addInlineMarker(this, true)
                    val lambdaParameters = info.addAllParameters(nodeRemapper)

                    val newCapturedRemapper = InlinedLambdaRemapper(
                            info.lambdaClassType.internalName, nodeRemapper, lambdaParameters,
                            info is DefaultLambda && info.isBoundCallableReference
                    )

                    setLambdaInlining(true)
                    val lambdaSMAP = info.node.classSMAP

                    val sourceMapper = if (inliningContext.classRegeneration && !inliningContext.isInliningLambda)
                        NestedSourceMapper(sourceMapper, lambdaSMAP.intervals, lambdaSMAP.sourceInfo)
                    else
                        InlineLambdaSourceMapper(sourceMapper.parent!!, info.node)
                    val inliner = MethodInliner(
                            info.node.node, lambdaParameters, inliningContext.subInlineLambda(info),
                            newCapturedRemapper, true /*cause all calls in same module as lambda*/,
                            "Lambda inlining " + info.lambdaClassType.internalName,
                            sourceMapper, inlineCallSiteInfo, null
                    )

                    val varRemapper = LocalVarRemapper(lambdaParameters, valueParamShift)
                    //TODO add skipped this and receiver
                    val lambdaResult = inliner.doInline(this.mv, varRemapper, true, info, invokeCall.finallyDepthShift)
                    result.mergeWithNotChangeInfo(lambdaResult)
                    result.reifiedTypeParametersUsages.mergeAll(lambdaResult.reifiedTypeParametersUsages)

                    //return value boxing/unboxing
                    val bridge = typeMapper.mapAsmMethod(ClosureCodegen.getErasedInvokeFunction(info.invokeMethodDescriptor))
                    StackValue.onStack(info.invokeMethod.returnType).put(bridge.returnType, this)
                    setLambdaInlining(false)
                    addInlineMarker(this, false)
                    sourceMapper.endMapping()
                    inlineOnlySmapSkipper?.markCallSiteLineNumber(remappingMethodAdapter)
                }
                else if (isAnonymousConstructorCall(owner, name)) { //TODO add method
                    //TODO add proper message
                    assert(transformationInfo is AnonymousObjectTransformationInfo) {
                        " call doesn't correspond to object transformation info for '$owner.$name': $transformationInfo"
                    }
                    val parent = inliningContext.parent
                    val shouldRegenerate = transformationInfo!!.shouldRegenerate(isSameModule)
                    val isContinuation = parent != null && parent.isContinuation
                    if (shouldRegenerate || isContinuation) {
                        assert(shouldRegenerate || inlineCallSiteInfo.ownerClassName == transformationInfo!!.oldClassName) { "Only coroutines can call their own constructors" }

                        //put additional captured parameters on stack
                        var info = transformationInfo as AnonymousObjectTransformationInfo

                        val oldInfo = inliningContext.findAnonymousObjectTransformationInfo(owner)
                        if (oldInfo != null && isContinuation) {
                            info = oldInfo
                        }

                        for (capturedParamDesc in info.allRecapturedParameters) {
                            visitFieldInsn(
                                    Opcodes.GETSTATIC, capturedParamDesc.containingLambdaName,
                                    CAPTURED_FIELD_FOLD_PREFIX + capturedParamDesc.fieldName, capturedParamDesc.type.descriptor
                            )
                        }
                        super.visitMethodInsn(opcode, info.newClassName, name, info.newConstructorDescriptor, itf)

                        //TODO: add new inner class also for other contexts
                        if (inliningContext.parent is RegeneratedClassContext) {
                            inliningContext.parent.typeRemapper.addAdditionalMappings(
                                    transformationInfo!!.oldClassName, transformationInfo!!.newClassName
                            )
                        }

                        transformationInfo = null
                    }
                    else {
                        super.visitMethodInsn(opcode, owner, name, desc, itf)
                    }
                }
                else if ((!inliningContext.isInliningLambda || isDefaultLambdaWithReification(inliningContext.lambdaInfo!!)) &&
                         ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false))) {
                    //we shouldn't process here content of inlining lambda it should be reified at external level except default lambdas
                }
                else {
                    super.visitMethodInsn(opcode, owner, name, desc, itf)
                }
            }

            override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) {
                if (opcode == Opcodes.GETSTATIC && (isAnonymousSingletonLoad(owner, name) || isWhenMappingAccess(owner, name))) {
                    handleAnonymousObjectRegeneration()
                }
                super.visitFieldInsn(opcode, owner, name, desc)
            }

            override fun visitMaxs(stack: Int, locals: Int) {
                lambdasFinallyBlocks = resultNode.tryCatchBlocks.size
                super.visitMaxs(stack, locals)
            }
        }

        node.accept(lambdaInliner)

        return resultNode
    }

    private fun isDefaultLambdaWithReification(lambdaInfo: LambdaInfo) =
            lambdaInfo is DefaultLambda && lambdaInfo.needReification

    private fun prepareNode(node: MethodNode, finallyDeepShift: Int): MethodNode {
        node.instructions.resetLabels()

        val capturedParamsSize = parameters.capturedParametersSizeOnStack
        val realParametersSize = parameters.realParametersSizeOnStack

        val transformedNode = object : MethodNode(
                API, node.access, node.name,
                Type.getMethodDescriptor(Type.getReturnType(node.desc), *(Type.getArgumentTypes(node.desc) + parameters.capturedTypes)),
                node.signature, node.exceptions?.toTypedArray()
        ) {
            private val GENERATE_DEBUG_INFO = GENERATE_SMAP && inlineOnlySmapSkipper == null

            private val isInliningLambda = nodeRemapper.isInsideInliningLambda

            private fun getNewIndex(`var`: Int): Int {
                return `var` + if (`var` < realParametersSize) 0 else capturedParamsSize
            }

            override fun visitVarInsn(opcode: Int, `var`: Int) {
                super.visitVarInsn(opcode, getNewIndex(`var`))
            }

            override fun visitIincInsn(`var`: Int, increment: Int) {
                super.visitIincInsn(getNewIndex(`var`), increment)
            }

            override fun visitMaxs(maxStack: Int, maxLocals: Int) {
                super.visitMaxs(maxStack, maxLocals + capturedParamsSize)
            }

            override fun visitLineNumber(line: Int, start: Label) {
                if (isInliningLambda || GENERATE_DEBUG_INFO) {
                    super.visitLineNumber(line, start)
                }
            }

            override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
                if (DEFAULT_LAMBDA_FAKE_CALL == owner) {
                    val index = name.substringAfter(DEFAULT_LAMBDA_FAKE_CALL).toInt()
                    val lambda = getLambdaIfExists(index) as DefaultLambda
                    lambda.parameterOffsetsInDefault.zip(lambda.capturedVars).asReversed().forEach {
                        (_, captured) ->
                        val originalBoundReceiverType = lambda.originalBoundReceiverType
                        if (lambda.isBoundCallableReference && AsmUtil.isPrimitive(originalBoundReceiverType)) {
                            StackValue.onStack(originalBoundReceiverType!!).put(captured.type, InstructionAdapter(this))
                        }
                        super.visitFieldInsn(
                                Opcodes.PUTSTATIC, captured.containingLambdaName, CAPTURED_FIELD_FOLD_PREFIX + captured.fieldName, captured.type.descriptor
                        )
                    }
                }
                else {
                    super.visitMethodInsn(opcode, owner, name, desc, itf)
                }
            }

            override fun visitLocalVariable(
                    name: String, desc: String, signature: String?, start: Label, end: Label, index: Int
            ) {
                if (isInliningLambda || GENERATE_DEBUG_INFO) {
                    val varSuffix = if (inliningContext.isRoot && !isFakeLocalVariableForInline(name)) INLINE_FUN_VAR_SUFFIX else ""
                    val varName = if (!varSuffix.isEmpty() && name == "this") name + "_" else name
                    super.visitLocalVariable(varName + varSuffix, desc, signature, start, end, getNewIndex(index))
                }
            }
        }

        node.accept(transformedNode)

        transformCaptured(transformedNode)
        transformFinallyDeepIndex(transformedNode, finallyDeepShift)

        return transformedNode
    }

    private fun markPlacesForInlineAndRemoveInlinable(
            node: MethodNode, labelOwner: LabelOwner, finallyDeepShift: Int
    ): MethodNode {
        val processingNode = prepareNode(node, finallyDeepShift)

        normalizeLocalReturns(processingNode, labelOwner)

        val sources = analyzeMethodNodeWithoutMandatoryTransformations(processingNode)

        val toDelete = SmartSet.create()
        val instructions = processingNode.instructions

        var awaitClassReification = false
        var currentFinallyDeep = 0

        InsnSequence(instructions).forEach { cur ->
            val frame = sources[instructions.indexOf(cur)]

            if (frame != null) {
                if (ReifiedTypeInliner.isNeedClassReificationMarker(cur)) {
                    awaitClassReification = true
                }
                else if (cur is MethodInsnNode) {
                    if (isFinallyStart(cur)) {
                        //TODO deep index calc could be more precise
                        currentFinallyDeep = getConstant(cur.previous)
                    }

                    val owner = cur.owner
                    val name = cur.name
                    //TODO check closure
                    val argTypes = Type.getArgumentTypes(cur.desc)
                    val paramCount = argTypes.size + 1//non static
                    val firstParameterIndex = frame.stackSize - paramCount
                    if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) {
                        val sourceValue = frame.getStack(firstParameterIndex)
                        val lambdaInfo = getLambdaIfExistsAndMarkInstructions(sourceValue, true, instructions, sources, toDelete)
                        invokeCalls.add(InvokeCall(lambdaInfo, currentFinallyDeep))
                    }
                    else if (isAnonymousConstructorCall(owner, name)) {
                        val lambdaMapping = HashMap()

                        var offset = 0
                        var capturesAnonymousObjectThatMustBeRegenerated = false
                        for (i in 0..paramCount - 1) {
                            val sourceValue = frame.getStack(firstParameterIndex + i)
                            val lambdaInfo = getLambdaIfExistsAndMarkInstructions(sourceValue, false, instructions, sources, toDelete
                            )
                            if (lambdaInfo != null) {
                                lambdaMapping.put(offset, lambdaInfo)
                            }
                            else if (i < argTypes.size && isAnonymousClassThatMustBeRegenerated(argTypes[i])) {
                                capturesAnonymousObjectThatMustBeRegenerated = true
                            }

                            offset += if (i == 0) 1 else argTypes[i - 1].size
                        }

                        transformations.add(
                                buildConstructorInvocation(
                                        owner, cur.desc, lambdaMapping, awaitClassReification, capturesAnonymousObjectThatMustBeRegenerated
                                )
                        )
                        awaitClassReification = false
                    }
                    else if (inliningContext.isInliningLambda && ReifiedTypeInliner.isOperationReifiedMarker(cur)) {
                        val reificationArgument = cur.reificationArgument
                        val parameterName = reificationArgument!!.parameterName
                        result.reifiedTypeParametersUsages.addUsedReifiedParameter(parameterName)
                    }
                }
                else if (cur.opcode == Opcodes.GETSTATIC) {
                    val fieldInsnNode = cur as FieldInsnNode?
                    val className = fieldInsnNode!!.owner
                    if (isAnonymousSingletonLoad(className, fieldInsnNode.name)) {
                        transformations.add(
                                AnonymousObjectTransformationInfo(
                                        className, awaitClassReification, isAlreadyRegenerated(className), true,
                                        inliningContext.nameGenerator
                                )
                        )
                        awaitClassReification = false
                    }
                    else if (isWhenMappingAccess(className, fieldInsnNode.name)) {
                        transformations.add(
                                WhenMappingTransformationInfo(
                                        className, inliningContext.nameGenerator, isAlreadyRegenerated(className), fieldInsnNode
                                )
                        )
                    }
                }
                else if (cur.opcode == Opcodes.POP) {
                    getLambdaIfExistsAndMarkInstructions(frame.top()!!, true, instructions, sources, toDelete)?.let {
                        toDelete.add(cur)
                    }
                }
            }
            else {
                //given frame is null if and only if the corresponding instruction cannot be reached (dead code).
                //clean dead code otherwise there is problems in unreachable finally block, don't touch label it cause try/catch/finally problems
                if (cur.type == AbstractInsnNode.LABEL) {
                    //NB: Cause we generate exception table for default handler using gaps (see ExpressionCodegen.visitTryExpression)
                    //it may occurs that interval for default handler starts before catch start label, so this label seems as dead,
                    //but as result all this labels will be merged into one (see KT-5863)
                }
                else {
                    toDelete.add(cur)
                }
            }
        }

        processingNode.remove(toDelete)

        //clean dead try/catch blocks
        processingNode.tryCatchBlocks.removeIf { it.isMeaningless() }

        return processingNode
    }

    private fun normalizeLocalReturns(node: MethodNode, labelOwner: LabelOwner) {
        val frames = analyzeMethodNodeBeforeInline(node)

        val localReturnsNormalizer = LocalReturnsNormalizer()

        for ((index, insnNode) in node.instructions.toArray().withIndex()) {
            val frame = frames[index] ?: continue
            // Don't care about dead code, it will be eliminated

            if (!isReturnOpcode(insnNode.opcode)) continue

            var insertBeforeInsn = insnNode

            // TODO extract isLocalReturn / isNonLocalReturn, see processReturns
            val labelName = getMarkedReturnLabelOrNull(insnNode)
            if (labelName != null) {
                if (!labelOwner.isMyLabel(labelName)) continue
                insertBeforeInsn = insnNode.previous
            }

            localReturnsNormalizer.addLocalReturnToTransform(insnNode, insertBeforeInsn, frame)
        }

        localReturnsNormalizer.transform(node)
    }

    private fun isAnonymousClassThatMustBeRegenerated(type: Type?): Boolean {
        if (type == null || type.sort != Type.OBJECT) return false
        val info = inliningContext.findAnonymousObjectTransformationInfo(type.internalName)
        return info != null && info.shouldRegenerate(true)
    }

    private fun analyzeMethodNodeBeforeInline(node: MethodNode): Array?> {
        try {
            FixStackWithLabelNormalizationMethodTransformer().transform("fake", node)
        }
        catch (e: Throwable) {
            throw wrapException(e, node, "couldn't inline method call")
        }

        return analyzeMethodNodeWithoutMandatoryTransformations(node)
    }

    private fun buildConstructorInvocation(
            anonymousType: String,
            desc: String,
            lambdaMapping: Map,
            needReification: Boolean,
            capturesAnonymousObjectThatMustBeRegenerated: Boolean
    ): AnonymousObjectTransformationInfo {
        val memoizeAnonymousObject = inliningContext.findAnonymousObjectTransformationInfo(anonymousType) == null

        val info = AnonymousObjectTransformationInfo(
                anonymousType, needReification, lambdaMapping,
                inliningContext.classRegeneration,
                isAlreadyRegenerated(anonymousType),
                desc,
                false,
                inliningContext.nameGenerator,
                capturesAnonymousObjectThatMustBeRegenerated
        )

        if (memoizeAnonymousObject) {
            inliningContext.root.internalNameToAnonymousObjectTransformationInfo.put(anonymousType, info)
        }
        return info
    }

    private fun isAlreadyRegenerated(owner: String): Boolean {
        return inliningContext.typeRemapper.hasNoAdditionalMapping(owner)
    }

    internal fun getLambdaIfExists(insnNode: AbstractInsnNode): LambdaInfo? {
        return when {
            insnNode.opcode == Opcodes.ALOAD ->
                getLambdaIfExists((insnNode as VarInsnNode).`var`)
            insnNode is FieldInsnNode && insnNode.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX) ->
                findCapturedField(insnNode, nodeRemapper).lambda
            else ->
                null
        }
    }

    private fun getLambdaIfExists(varIndex: Int): LambdaInfo? {
        if (varIndex < parameters.argsSizeOnStack) {
            return parameters.getParameterByDeclarationSlot(varIndex).lambda
        }
        return null
    }

    private fun transformCaptured(node: MethodNode) {
        if (nodeRemapper.isRoot) {
            return
        }

        // Fold all captured variables access chains
        //          ALOAD 0
        //          [ALOAD this$0]*
        //          GETFIELD $captured
        //  to GETFIELD $$$$captured
        // On future decoding this field could be inlined or unfolded to another field access chain
        // (this chain could differ cause some of this$0 could be inlined)
        var cur: AbstractInsnNode? = node.instructions.first
        while (cur != null) {
            if (cur is VarInsnNode && cur.opcode == Opcodes.ALOAD) {
                val varIndex = cur.`var`
                if (varIndex == 0 || nodeRemapper.shouldProcessNonAload0FieldAccessChains()) {
                    val accessChain = getCapturedFieldAccessChain((cur as VarInsnNode?)!!)
                    val insnNode = nodeRemapper.foldFieldAccessChainIfNeeded(accessChain, node)
                    if (insnNode != null) {
                        cur = insnNode
                    }
                }
            }
            cur = cur.next
        }
    }

    private fun wrapException(originalException: Throwable, node: MethodNode, errorSuffix: String): RuntimeException {
        if (originalException is InlineException) {
            return InlineException("$errorPrefix: $errorSuffix", originalException)
        }
        else {
            return InlineException("$errorPrefix: $errorSuffix\nCause: ${node.nodeText}", originalException)
        }
    }

    private class LocalReturnsNormalizer {
        private class LocalReturn(
                private val returnInsn: AbstractInsnNode,
                private val insertBeforeInsn: AbstractInsnNode,
                private val frame: Frame
        ) {

            fun transform(insnList: InsnList, returnVariableIndex: Int) {
                val isReturnWithValue = returnInsn.opcode != Opcodes.RETURN

                val expectedStackSize = if (isReturnWithValue) 1 else 0
                val actualStackSize = frame.stackSize
                if (expectedStackSize == actualStackSize) return

                var stackSize = actualStackSize
                if (isReturnWithValue) {
                    val storeOpcode = Opcodes.ISTORE + returnInsn.opcode - Opcodes.IRETURN
                    insnList.insertBefore(insertBeforeInsn, VarInsnNode(storeOpcode, returnVariableIndex))
                    stackSize--
                }

                while (stackSize > 0) {
                    val stackElementSize = frame.getStack(stackSize - 1).getSize()
                    val popOpcode = if (stackElementSize == 1) Opcodes.POP else Opcodes.POP2
                    insnList.insertBefore(insertBeforeInsn, InsnNode(popOpcode))
                    stackSize--
                }

                if (isReturnWithValue) {
                    val loadOpcode = Opcodes.ILOAD + returnInsn.opcode - Opcodes.IRETURN
                    insnList.insertBefore(insertBeforeInsn, VarInsnNode(loadOpcode, returnVariableIndex))
                }
            }
        }

        private val localReturns = SmartList()

        private var returnVariableSize = 0
        private var returnOpcode = -1

        internal fun addLocalReturnToTransform(
                returnInsn: AbstractInsnNode,
                insertBeforeInsn: AbstractInsnNode,
                sourceValueFrame: Frame
        ) {
            assert(isReturnOpcode(returnInsn.opcode)) { "return instruction expected" }
            assert(returnOpcode < 0 || returnOpcode == returnInsn.opcode) { "Return op should be " + Printer.OPCODES[returnOpcode] + ", got " + Printer.OPCODES[returnInsn.opcode] }
            returnOpcode = returnInsn.opcode

            localReturns.add(LocalReturn(returnInsn, insertBeforeInsn, sourceValueFrame))

            if (returnInsn.opcode != Opcodes.RETURN) {
                if (returnInsn.opcode == Opcodes.LRETURN || returnInsn.opcode == Opcodes.DRETURN) {
                    returnVariableSize = 2
                }
                else {
                    returnVariableSize = 1
                }
            }
        }

        fun transform(methodNode: MethodNode) {
            var returnVariableIndex = -1
            if (returnVariableSize > 0) {
                returnVariableIndex = methodNode.maxLocals
                methodNode.maxLocals += returnVariableSize
            }

            for (localReturn in localReturns) {
                localReturn.transform(methodNode.instructions, returnVariableIndex)
            }
        }
    }

    //Place to insert finally blocks from try blocks that wraps inline fun call
    class PointForExternalFinallyBlocks(
            @JvmField val beforeIns: AbstractInsnNode,
            @JvmField val returnType: Type,
            @JvmField val finallyIntervalEnd: LabelNode
    )

    companion object {

        @JvmStatic
        fun findCapturedField(node: FieldInsnNode, fieldRemapper: FieldRemapper): CapturedParamInfo {
            assert(node.name.startsWith(CAPTURED_FIELD_FOLD_PREFIX)) {
                "Captured field template should start with $CAPTURED_FIELD_FOLD_PREFIX prefix"
            }
            val fin = FieldInsnNode(node.opcode, node.owner, node.name.substring(3), node.desc)
            val field = fieldRemapper.findField(fin) ?: throw IllegalStateException(
                    "Couldn't find captured field ${node.owner}.${node.name} in ${fieldRemapper.originalLambdaInternalName}"
            )
            return field
        }

        private fun analyzeMethodNodeWithoutMandatoryTransformations(node: MethodNode): Array?> {
            val analyzer = object : Analyzer(SourceInterpreter()) {
                override fun newFrame(nLocals: Int, nStack: Int): Frame {
                    return object : Frame(nLocals, nStack) {
                        @Throws(AnalyzerException::class)
                        override fun execute(insn: AbstractInsnNode, interpreter: Interpreter) {
                            // This can be a void non-local return from a non-void method; Frame#execute would throw and do nothing else.
                            if (insn.opcode == Opcodes.RETURN) return
                            super.execute(insn, interpreter)
                        }
                    }
                }
            }

            try {
                return analyzer.analyze("fake", node)
            }
            catch (e: AnalyzerException) {
                throw RuntimeException(e)
            }

        }

        //remove next template:
        //      aload x
        //      LDC paramName
        //      INTRINSICS_CLASS_NAME.checkParameterIsNotNull(...)
        private fun removeClosureAssertions(node: MethodNode) {
            val toDelete = arrayListOf()
            InsnSequence(node.instructions).filterIsInstance().forEach {
                methodInsnNode ->
                if (methodInsnNode.name == "checkParameterIsNotNull" && methodInsnNode.owner == IntrinsicMethods.INTRINSICS_CLASS_NAME) {
                    val prev = methodInsnNode.previous
                    assert(Opcodes.LDC == prev?.opcode) { "'checkParameterIsNotNull' should go after LDC but $prev" }
                    val prevPev = methodInsnNode.previous.previous
                    assert(Opcodes.ALOAD == prevPev?.opcode) { "'checkParameterIsNotNull' should be invoked on local var, but $prev" }

                    toDelete.add(prevPev)
                    toDelete.add(prev)
                    toDelete.add(methodInsnNode)
                }
            }

            node.remove(toDelete)
        }

        private fun transformFinallyDeepIndex(node: MethodNode, finallyDeepShift: Int) {
            if (finallyDeepShift == 0) {
                return
            }

            var cur: AbstractInsnNode? = node.instructions.first
            while (cur != null) {
                if (cur is MethodInsnNode && isFinallyMarker(cur)) {
                    val constant = cur.previous
                    val curDeep = getConstant(constant)
                    node.instructions.insert(constant, LdcInsnNode(curDeep + finallyDeepShift))
                    node.instructions.remove(constant)
                }
                cur = cur.next
            }
        }

        private fun getCapturedFieldAccessChain(aload0: VarInsnNode): List {
            val lambdaAccessChain = mutableListOf(aload0).apply {
                addAll(InsnSequence(aload0.next, null).filter { it.isMeaningful }.takeWhile {
                    insnNode ->
                    insnNode is FieldInsnNode && "this$0" == insnNode.name
                }.toList())
            }

            return lambdaAccessChain.apply {
                last().getNextMeaningful().takeIf { insn -> insn is FieldInsnNode }?.also {
                    //captured field access
                    insn -> add(insn)
                }
            }
        }

        private fun putStackValuesIntoLocals(
                directOrder: List, shift: Int, iv: InstructionAdapter, descriptor: String
        ) {
            val actualParams = Type.getArgumentTypes(descriptor)
            assert(actualParams.size == directOrder.size) {
                "Number of expected and actual params should be equals, but ${actualParams.size} != ${directOrder.size}}!"
            }

            var currentShift = shift + directOrder.sumBy { it.size }

            directOrder.asReversed().forEachIndexed { index, type ->
                currentShift -= type.size
                val typeOnStack = actualParams[index]
                if (typeOnStack != type) {
                    StackValue.onStack(typeOnStack).put(type, iv)
                }
                iv.store(currentShift, type)
            }
        }

        //process local and global returns (local substituted with goto end-label global kept unchanged)
        @JvmStatic
        fun processReturns(
                node: MethodNode, labelOwner: LabelOwner, remapReturn: Boolean, endLabel: Label?
        ): List {
            if (!remapReturn) {
                return emptyList()
            }
            val result = ArrayList()
            val instructions = node.instructions
            var insnNode: AbstractInsnNode? = instructions.first
            while (insnNode != null) {
                if (isReturnOpcode(insnNode.opcode)) {
                    var isLocalReturn = true
                    val labelName = getMarkedReturnLabelOrNull(insnNode)

                    if (labelName != null) {
                        isLocalReturn = labelOwner.isMyLabel(labelName)
                        //remove global return flag
                        if (isLocalReturn) {
                            instructions.remove(insnNode.previous)
                        }
                    }

                    if (isLocalReturn && endLabel != null) {
                        val nop = InsnNode(Opcodes.NOP)
                        instructions.insert(insnNode, nop)

                        val labelNode = endLabel.info as LabelNode
                        val jumpInsnNode = JumpInsnNode(Opcodes.GOTO, labelNode)
                        instructions.insert(nop, jumpInsnNode)

                        instructions.remove(insnNode)
                        insnNode = jumpInsnNode
                    }

                    //generate finally block before nonLocalReturn flag/return/goto
                    val label = LabelNode()
                    instructions.insert(insnNode, label)
                    result.add(PointForExternalFinallyBlocks(
                            getInstructionToInsertFinallyBefore(insnNode, isLocalReturn), getReturnType(insnNode.opcode), label
                    ))
                }
                insnNode = insnNode.next
            }
            return result
        }

        private fun getInstructionToInsertFinallyBefore(nonLocalReturnOrJump: AbstractInsnNode, isLocal: Boolean): AbstractInsnNode {
            return if (isLocal) nonLocalReturnOrJump else nonLocalReturnOrJump.previous
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy