diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/InlineCycleReporter.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/InlineCycleReporter.kt index f061dce31d1..f48bddd700f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/InlineCycleReporter.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/InlineCycleReporter.kt @@ -16,37 +16,36 @@ package org.jetbrains.kotlin.codegen -import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.codegen.state.GenerationState -import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.codegen.inline.InlineCall import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.Errors -import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall class InlineCycleReporter(private val diagnostics: DiagnosticSink) { - private val processingFunctions = linkedMapOf() + private val processingFunctions = linkedMapOf() - fun enterIntoInlining(call: ResolvedCall<*>?): Boolean { - //null call for default method inlining - if (call != null) { - val callElement = call.call.callElement - if (processingFunctions.contains(callElement)) { - val cycle = processingFunctions.asSequence().dropWhile { it.key != callElement } - cycle.forEach { - diagnostics.report(Errors.INLINE_CALL_CYCLE.on(it.key, it.value)) + fun enterIntoInlining(call: InlineCall?): Boolean { + // null call for default method inlining + val id = call?.id + if (id != null) { + if (processingFunctions.contains(id)) { + val cycle = processingFunctions.values.dropWhile { it.id != id } + for (cycleCall in cycle) { + val callPsiElement = cycleCall.callElement + if (callPsiElement != null) { + diagnostics.report(Errors.INLINE_CALL_CYCLE.on(callPsiElement, cycleCall.calleeDescriptor)) + } } return false } - processingFunctions.put(callElement, call.resultingDescriptor.original) + processingFunctions[id] = call } return true } - fun exitFromInliningOf(call: ResolvedCall<*>?) { + fun exitFromInliningOf(call: InlineCall?) { if (call != null) { - val callElement = call.call.callElement - processingFunctions.remove(callElement) + processingFunctions.remove(call.id) } } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/GlobalInlineContext.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/GlobalInlineContext.kt index 62c8f68515a..7eaaa576562 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/GlobalInlineContext.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/GlobalInlineContext.kt @@ -16,12 +16,12 @@ class GlobalInlineContext(diagnostics: DiagnosticSink) { private val typesUsedInInlineFunctions = LinkedList>() - fun enterIntoInlining(call: ResolvedCall<*>?) = + fun enterIntoInlining(call: InlineCall?) = inlineCycleReporter.enterIntoInlining(call).also { if (it) typesUsedInInlineFunctions.push(hashSetOf()) } - fun exitFromInliningOf(call: ResolvedCall<*>?) { + fun exitFromInliningOf(call: InlineCall?) { inlineCycleReporter.exitFromInliningOf(call) val pop = typesUsedInInlineFunctions.pop() typesUsedInInlineFunctions.peek()?.addAll(pop) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCall.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCall.kt new file mode 100644 index 00000000000..d47d8458254 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCall.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.codegen.inline + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall + +interface InlineCall { + val id: Any + val calleeDescriptor: CallableDescriptor + val callElement: PsiElement? +} + +class InlineCallImpl( + override val calleeDescriptor: CallableDescriptor, + override val callElement: PsiElement +) : InlineCall { + + override val id: Any + get() = callElement + + companion object { + fun of(resolvedCall: ResolvedCall<*>?) = + resolvedCall?.run { + InlineCallImpl(resultingDescriptor.original, call.callElement) + } + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineCodegen.kt index fada3565752..2ab8b66df9a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineCodegen.kt @@ -60,14 +60,15 @@ class PsiInlineCodegen( callDefault: Boolean, codegen: ExpressionCodegen ) { - if (!state.globalInlineContext.enterIntoInlining(resolvedCall)) { + val inlineCall = InlineCallImpl.of(resolvedCall) + if (!state.globalInlineContext.enterIntoInlining(inlineCall)) { generateStub(resolvedCall, codegen) return } try { performInline(resolvedCall?.typeArguments?.keys?.toList(), callDefault, callDefault, codegen.typeSystem) } finally { - state.globalInlineContext.exitFromInliningOf(resolvedCall) + state.globalInlineContext.exitFromInliningOf(inlineCall) } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index d1f87abfb86..d2cfddf86fc 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -34,6 +34,7 @@ import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ConstructorDescriptor import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* @@ -643,7 +644,7 @@ class ExpressionCodegen( val isNonLocalReturn = methodSignatureMapper.mapFunctionName(owner) != methodSignatureMapper.mapFunctionName(irFunction) if (isNonLocalReturn && state.isInlineDisabled) { - //TODO: state.diagnostics.report(Errors.NON_LOCAL_RETURN_IN_DISABLED_INLINE.on(expression)) + context.psiErrorBuilder.at(expression, owner).report(Errors.NON_LOCAL_RETURN_IN_DISABLED_INLINE) genThrow( mv, "java/lang/UnsupportedOperationException", "Non-local returns are not allowed with inlining disabled" diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt index 914cb362b14..d3f9bc31b20 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt @@ -5,15 +5,14 @@ package org.jetbrains.kotlin.backend.jvm.codegen +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter import org.jetbrains.kotlin.backend.jvm.ir.isLambda -import org.jetbrains.kotlin.codegen.IrExpressionLambda -import org.jetbrains.kotlin.codegen.JvmKotlinType -import org.jetbrains.kotlin.codegen.StackValue -import org.jetbrains.kotlin.codegen.ValueKind +import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.inline.* import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin @@ -28,6 +27,7 @@ import org.jetbrains.kotlin.ir.types.toKotlinType import org.jetbrains.kotlin.ir.util.dump import org.jetbrains.kotlin.ir.util.getArgumentsWithIr import org.jetbrains.kotlin.ir.util.isSuspend +import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.org.objectweb.asm.Type @@ -43,15 +43,18 @@ class IrInlineCodegen( typeParameterMappings: TypeParameterMappings, sourceCompiler: SourceCompilerForInline, reifiedTypeInliner: ReifiedTypeInliner -) : InlineCodegen( - codegen, state, function.descriptor, methodOwner, signature, typeParameterMappings, sourceCompiler, reifiedTypeInliner -), IrCallGenerator { +) : + InlineCodegen( + codegen, state, function.descriptor, methodOwner, signature, typeParameterMappings, sourceCompiler, reifiedTypeInliner + ), + IrCallGenerator { + override fun generateAssertFieldIfNeeded(info: RootInliningContext) { if (info.generateAssertField && (sourceCompiler as IrSourceCompilerForInline).isPrimaryCopy) { - codegen.classCodegen.generateAssertFieldIfNeeded()?.let { + codegen.classCodegen.generateAssertFieldIfNeeded()?.run { // Generating right now, so no longer can insert the initializer into it. // Instead, ask ExpressionCodegen to generate the code for it directly. - it.accept(codegen, BlockInfo()).discard() + accept(codegen, BlockInfo()).discard() } } } @@ -149,13 +152,38 @@ class IrInlineCodegen( invocationParamBuilder.markValueParametersStart() } + private inner class IrInlineCall( + private val irFunctionAccessExpression: IrFunctionAccessExpression + ) : InlineCall { + + override val calleeDescriptor: CallableDescriptor = + irFunctionAccessExpression.symbol.descriptor.original + + override val callElement: PsiElement? + get() = + codegen.context.psiSourceManager.findPsiElement(irFunctionAccessExpression, function) + ?: codegen.context.psiSourceManager.findPsiElement(function) + + override val id: Any + get() = irFunctionAccessExpression + + override fun toString(): String = irFunctionAccessExpression.render() + } + override fun genCall( callableMethod: IrCallableMethod, codegen: ExpressionCodegen, expression: IrFunctionAccessExpression ) { - // TODO port inlining cycle detection to IrFunctionAccessExpression & pass it - state.globalInlineContext.enterIntoInlining(null) + val inlineCall = IrInlineCall(expression) + if (!state.globalInlineContext.enterIntoInlining(inlineCall)) { + AsmUtil.genThrow( + codegen.v, + "java/lang/UnsupportedOperationException", + "Call is a part of inline call cycle: ${expression.render()}" + ) + return + } try { performInline( expression.symbol.owner.typeParameters.map { it.symbol }, @@ -164,7 +192,7 @@ class IrInlineCodegen( codegen.typeMapper.typeSystem ) } finally { - state.globalInlineContext.exitFromInliningOf(null) + state.globalInlineContext.exitFromInliningOf(inlineCall) } } diff --git a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/PsiSourceManager.kt b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/PsiSourceManager.kt index bc0848bd49c..bf04c5972ed 100644 --- a/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/PsiSourceManager.kt +++ b/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/PsiSourceManager.kt @@ -119,14 +119,12 @@ class PsiSourceManager : SourceManager { fileEntriesByIrFile[irFile] fun findPsiElement(irElement: IrElement, irFile: IrFile, psiElementClass: KClass): E? { - val psiFileEntry = fileEntriesByIrFile[irFile] - ?: throw AssertionError("No PSI file for irFile $irFile") + val psiFileEntry = fileEntriesByIrFile[irFile] ?: return null return psiFileEntry.findPsiElement(irElement, psiElementClass) } fun findPsiElement(irElement: IrElement, irFile: IrFile): PsiElement? { - val psiFileEntry = fileEntriesByIrFile[irFile] - ?: throw AssertionError("No PSI file for irFile $irFile") + val psiFileEntry = fileEntriesByIrFile[irFile] ?: return null return psiFileEntry.findPsiElement(irElement) } diff --git a/compiler/testData/cli/jvm/inlineCycle.kt b/compiler/testData/cli/jvm/inlineCycle.kt index 09fc245f430..b5b3526dc8c 100644 --- a/compiler/testData/cli/jvm/inlineCycle.kt +++ b/compiler/testData/cli/jvm/inlineCycle.kt @@ -1,14 +1,17 @@ -inline fun a(l: () -> Unit) { - b(l) +inline fun a(q: () -> Unit) { + b(q) - //check that nested not recognized as cycle + // check that nested not recognized as cycle c { c { } } + + withDefaults() } +inline fun withDefaults(x: Int = 1) = x * 2 inline fun b(p: () -> Unit) { p() diff --git a/compiler/testData/cli/jvm/inlineCycle.out b/compiler/testData/cli/jvm/inlineCycle.out index 987fcd538bf..5c8928dd385 100644 --- a/compiler/testData/cli/jvm/inlineCycle.out +++ b/compiler/testData/cli/jvm/inlineCycle.out @@ -1,7 +1,7 @@ compiler/testData/cli/jvm/inlineCycle.kt:2:5: error: the 'b' invocation is a part of inline cycle - b(l) + b(q) ^ -compiler/testData/cli/jvm/inlineCycle.kt:15:5: error: the 'a' invocation is a part of inline cycle +compiler/testData/cli/jvm/inlineCycle.kt:18:5: error: the 'a' invocation is a part of inline cycle a(p) ^ COMPILATION_ERROR diff --git a/compiler/testData/cli/jvm/inlineCycle_ir.args b/compiler/testData/cli/jvm/inlineCycle_ir.args new file mode 100644 index 00000000000..bd33672d8ad --- /dev/null +++ b/compiler/testData/cli/jvm/inlineCycle_ir.args @@ -0,0 +1,4 @@ +$TESTDATA_DIR$/inlineCycle.kt +-d +$TEMP_DIR$ +-Xuse-ir diff --git a/compiler/testData/cli/jvm/inlineCycle_ir.out b/compiler/testData/cli/jvm/inlineCycle_ir.out new file mode 100644 index 00000000000..5c8928dd385 --- /dev/null +++ b/compiler/testData/cli/jvm/inlineCycle_ir.out @@ -0,0 +1,7 @@ +compiler/testData/cli/jvm/inlineCycle.kt:2:5: error: the 'b' invocation is a part of inline cycle + b(q) + ^ +compiler/testData/cli/jvm/inlineCycle.kt:18:5: error: the 'a' invocation is a part of inline cycle + a(p) + ^ +COMPILATION_ERROR diff --git a/compiler/testData/cli/jvm/nonLocalDisabled_ir.args b/compiler/testData/cli/jvm/nonLocalDisabled_ir.args new file mode 100644 index 00000000000..796cfbb1fe0 --- /dev/null +++ b/compiler/testData/cli/jvm/nonLocalDisabled_ir.args @@ -0,0 +1,5 @@ +$TESTDATA_DIR$/nonLocalDisabled.kt +-Xno-inline +-d +$TEMP_DIR$ +-Xuse-ir diff --git a/compiler/testData/cli/jvm/nonLocalDisabled_ir.out b/compiler/testData/cli/jvm/nonLocalDisabled_ir.out new file mode 100644 index 00000000000..a7cfcfadfa5 --- /dev/null +++ b/compiler/testData/cli/jvm/nonLocalDisabled_ir.out @@ -0,0 +1,7 @@ +compiler/testData/cli/jvm/nonLocalDisabled.kt:3:9: error: non-local returns are not allowed with inlining disabled + return + ^ +compiler/testData/cli/jvm/nonLocalDisabled.kt:7:9: error: non-local returns are not allowed with inlining disabled + return@a + ^ +COMPILATION_ERROR diff --git a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java index 9c88429404a..9318faee830 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java @@ -305,6 +305,11 @@ public class CliTestGenerated extends AbstractCliTest { runTest("compiler/testData/cli/jvm/inlineCycle.args"); } + @TestMetadata("inlineCycle_ir.args") + public void testInlineCycle_ir() throws Exception { + runTest("compiler/testData/cli/jvm/inlineCycle_ir.args"); + } + @TestMetadata("internalArgDisableLanguageFeature.args") public void testInternalArgDisableLanguageFeature() throws Exception { runTest("compiler/testData/cli/jvm/internalArgDisableLanguageFeature.args"); @@ -555,6 +560,11 @@ public class CliTestGenerated extends AbstractCliTest { runTest("compiler/testData/cli/jvm/nonLocalDisabled.args"); } + @TestMetadata("nonLocalDisabled_ir.args") + public void testNonLocalDisabled_ir() throws Exception { + runTest("compiler/testData/cli/jvm/nonLocalDisabled_ir.args"); + } + @TestMetadata("nonexistentPathInModule.args") public void testNonexistentPathInModule() throws Exception { runTest("compiler/testData/cli/jvm/nonexistentPathInModule.args");