diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/InlineCycleReporter.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/InlineCycleReporter.kt new file mode 100644 index 00000000000..ffe1036ff5f --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/InlineCycleReporter.kt @@ -0,0 +1,52 @@ +/* + * 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 + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall + +public class InlineCycleReporter(val diagnostics: DiagnosticSink) { + + val processingFunctions = linkedMapOf() + + public fun enterIntoInlining(call: ResolvedCall<*>?): Boolean { + //null call for default method inlining + if (call != null) { + val callElement = call.getCall().getCallElement() + if (processingFunctions.contains(callElement)) { + val cycle = processingFunctions.asSequence().dropWhile { it.getKey() != callElement } + cycle.forEach { + diagnostics.report(Errors.INLINE_CALL_CYCLE.on(it.getKey(), it.getValue())) + } + return false + } + processingFunctions.put(callElement, call.getResultingDescriptor().getOriginal()) + } + return true + } + + public fun exitFromInliningOf(call: ResolvedCall<*>?) { + if (call != null) { + val callElement = call.getCall().getCallElement() + processingFunctions.remove(callElement) + } + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java index 112329cbaa6..2ae8f5bdf59 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java @@ -127,6 +127,10 @@ public class InlineCodegen extends CallGenerator { @Override public void genCallInner(@NotNull Callable callableMethod, @Nullable ResolvedCall resolvedCall, boolean callDefault, @NotNull ExpressionCodegen codegen) { SMAPAndMethodNode nodeAndSmap = null; + if (!state.getInlineCycleReporter().enterIntoInlining(resolvedCall)) { + generateStub(resolvedCall, codegen); + return; + } try { nodeAndSmap = createMethodNode(callDefault); @@ -144,8 +148,16 @@ public class InlineCodegen extends CallGenerator { (generateNodeText ? ("\ncause: " + InlineCodegenUtil.getNodeText(nodeAndSmap != null ? nodeAndSmap.getNode(): null)) : ""), e, callElement); } + finally { + state.getInlineCycleReporter().exitFromInliningOf(resolvedCall); + } + } - + protected void generateStub(@Nullable ResolvedCall resolvedCall, @NotNull ExpressionCodegen codegen) { + leaveTemps(); + assert resolvedCall != null; + String message = "Call is part of inline cycle: " + resolvedCall.getCall().getCallElement().getText(); + AsmUtil.genThrow(codegen.v, "java/lang/UnsupportedOperationException", message); } private void endCall(@NotNull InlineResult result) { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.java index 12d623813e5..9bd224f3f91 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.java @@ -97,6 +97,9 @@ public class GenerationState { @NotNull private final SamWrapperClasses samWrapperClasses = new SamWrapperClasses(this); + @NotNull + private final InlineCycleReporter inlineCycleReporter; + @NotNull private final MappingsClassesForWhenByEnum mappingsClassesForWhenByEnum = new MappingsClassesForWhenByEnum(this); @@ -194,6 +197,7 @@ public class GenerationState { this.reflectionTypes = new ReflectionTypes(module); this.runtimeTypes = new JvmRuntimeTypes(reflectionTypes); + this.inlineCycleReporter = new InlineCycleReporter(diagnostics); } @NotNull @@ -246,6 +250,11 @@ public class GenerationState { return samWrapperClasses; } + @NotNull + public InlineCycleReporter getInlineCycleReporter() { + return inlineCycleReporter; + } + @NotNull public MappingsClassesForWhenByEnum getMappingsClassesForWhenByEnum() { return mappingsClassesForWhenByEnum; diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index e7cb241c7ee..d17fb7e6d2d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -675,6 +675,9 @@ public interface Errors { DiagnosticFactory2 NULLABLE_INLINE_PARAMETER = DiagnosticFactory2.create(ERROR); DiagnosticFactory2 RECURSION_IN_INLINE = DiagnosticFactory2.create(ERROR); DiagnosticFactory0 DECLARATION_CANT_BE_INLINED = DiagnosticFactory0.create(ERROR); + DiagnosticFactory1 INLINE_CALL_CYCLE = DiagnosticFactory1.create(ERROR, DEFAULT); + DiagnosticFactory0 NON_LOCAL_RETURN_IN_DISABLED_INLINE = DiagnosticFactory0.create(ERROR); + // Error sets ImmutableSet> UNRESOLVED_REFERENCE_DIAGNOSTICS = ImmutableSet.of( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index 4ca0dcfeb87..afbfb6e4efb 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -612,6 +612,7 @@ public class DefaultErrorMessages { MAP.put(RECURSION_IN_INLINE, "Inline-function ''{1}'' can't be recursive", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES); //Inline non Locals MAP.put(NON_LOCAL_RETURN_NOT_ALLOWED, "Can''t inline ''{0}'' here: it may contain non-local returns. Annotate parameter declaration ''{0}'' with ''inlineOptions(ONLY_LOCAL_RETURN)''", ELEMENT_TEXT, SHORT_NAMES_IN_TYPES, SHORT_NAMES_IN_TYPES); + MAP.put(INLINE_CALL_CYCLE, "The ''{0}'' invocation is a part of inline cycle", NAME); MAP.setImmutable(); diff --git a/compiler/testData/cli/js/inlineCycle.args b/compiler/testData/cli/js/inlineCycle.args new file mode 100644 index 00000000000..cc52db9f8b0 --- /dev/null +++ b/compiler/testData/cli/js/inlineCycle.args @@ -0,0 +1,3 @@ +$TESTDATA_DIR$/inlineCycle.kt +-output +$TEMP_DIR$ diff --git a/compiler/testData/cli/js/inlineCycle.kt b/compiler/testData/cli/js/inlineCycle.kt new file mode 100644 index 00000000000..5a92ab84ba5 --- /dev/null +++ b/compiler/testData/cli/js/inlineCycle.kt @@ -0,0 +1,20 @@ +inline fun a(l: () -> Unit) { + b(l) + + //check taht nested not recognized as cycle + c { + c { + + } + } +} + + +inline fun b(p: () -> Unit) { + p() + a(p) +} + +inline fun c(p: () -> Unit) { + p() +} \ No newline at end of file diff --git a/compiler/testData/cli/js/inlineCycle.out b/compiler/testData/cli/js/inlineCycle.out new file mode 100644 index 00000000000..40c4b3122c6 --- /dev/null +++ b/compiler/testData/cli/js/inlineCycle.out @@ -0,0 +1,3 @@ +ERROR: compiler/testData/cli/js/inlineCycle.kt: (2, 5) The 'b' invocation is a part of inline cycle +ERROR: compiler/testData/cli/js/inlineCycle.kt: (15, 5) The 'a' invocation is a part of inline cycle +COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/testData/cli/jvm/inlineCycle.args b/compiler/testData/cli/jvm/inlineCycle.args new file mode 100644 index 00000000000..f979b6a7959 --- /dev/null +++ b/compiler/testData/cli/jvm/inlineCycle.args @@ -0,0 +1,3 @@ +$TESTDATA_DIR$/inlineCycle.kt +-d +$TEMP_DIR$ diff --git a/compiler/testData/cli/jvm/inlineCycle.kt b/compiler/testData/cli/jvm/inlineCycle.kt new file mode 100644 index 00000000000..5a92ab84ba5 --- /dev/null +++ b/compiler/testData/cli/jvm/inlineCycle.kt @@ -0,0 +1,20 @@ +inline fun a(l: () -> Unit) { + b(l) + + //check taht nested not recognized as cycle + c { + c { + + } + } +} + + +inline fun b(p: () -> Unit) { + p() + a(p) +} + +inline fun c(p: () -> Unit) { + p() +} \ No newline at end of file diff --git a/compiler/testData/cli/jvm/inlineCycle.out b/compiler/testData/cli/jvm/inlineCycle.out new file mode 100644 index 00000000000..88ae93d25a1 --- /dev/null +++ b/compiler/testData/cli/jvm/inlineCycle.out @@ -0,0 +1,3 @@ +ERROR: compiler/testData/cli/jvm/inlineCycle.kt: (2, 5) The 'b' invocation is a part of inline cycle +ERROR: compiler/testData/cli/jvm/inlineCycle.kt: (15, 5) The 'a' invocation is a part of inline cycle +COMPILATION_ERROR \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/cli/KotlincExecutableTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cli/KotlincExecutableTestGenerated.java index b9fc6b25e7c..fcc5d812712 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/KotlincExecutableTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/KotlincExecutableTestGenerated.java @@ -79,6 +79,12 @@ public class KotlincExecutableTestGenerated extends AbstractKotlincExecutableTes doJvmTest(fileName); } + @TestMetadata("inlineCycle.args") + public void testInlineCycle() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/cli/jvm/inlineCycle.args"); + doJvmTest(fileName); + } + @TestMetadata("multipleTextRangesInDiagnosticsOrder.args") public void testMultipleTextRangesInDiagnosticsOrder() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/cli/jvm/multipleTextRangesInDiagnosticsOrder.args"); @@ -202,6 +208,12 @@ public class KotlincExecutableTestGenerated extends AbstractKotlincExecutableTes doJsTest(fileName); } + @TestMetadata("inlineCycle.args") + public void testInlineCycle() throws Exception { + String fileName = JetTestUtils.navigationMetadata("compiler/testData/cli/js/inlineCycle.args"); + doJsTest(fileName); + } + @TestMetadata("jsExtraHelp.args") public void testJsExtraHelp() throws Exception { String fileName = JetTestUtils.navigationMetadata("compiler/testData/cli/js/jsExtraHelp.args"); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt index 1bbb748505b..a3b370d3b90 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/DefaultErrorMessagesJs.kt @@ -39,7 +39,6 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by Delegates.lazy { put(ErrorsJs.NON_TOPLEVEL_CLASS_DECLARATION, "Non-toplevel {0} declarations not supported yet", Renderers.STRING) put(ErrorsJs.SECONDARY_CONSTRUCTOR, "Secondary constructors not supported yet") put(ErrorsJs.JSCODE_NO_JAVASCRIPT_PRODUCED, "Argument must be non-empty JavaScript code") - put(ErrorsJs.INLINE_CALL_CYCLE, "The call is a part of inline cycle") this } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java index 28bd0420e92..843115d832d 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/ErrorsJs.java @@ -16,7 +16,6 @@ package org.jetbrains.kotlin.js.resolve.diagnostics; -import com.intellij.psi.PsiElement; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1; import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2; @@ -46,7 +45,6 @@ public interface ErrorsJs { DiagnosticFactory1 NON_TOPLEVEL_CLASS_DECLARATION = DiagnosticFactory1.create(ERROR, DECLARATION_NAME); DiagnosticFactory0 SECONDARY_CONSTRUCTOR = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE_OR_DEFAULT); DiagnosticFactory0 JSCODE_NO_JAVASCRIPT_PRODUCED = DiagnosticFactory0.create(ERROR, DEFAULT); - DiagnosticFactory0 INLINE_CALL_CYCLE = DiagnosticFactory0.create(ERROR, DEFAULT); @SuppressWarnings("UnusedDeclaration") Object _initializer = new Object() { diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java index 6d2cc7c8e3a..85a15d2345c 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/JsInliner.java @@ -19,17 +19,20 @@ package org.jetbrains.kotlin.js.inline; import com.google.dart.compiler.backend.js.ast.*; import com.google.dart.compiler.backend.js.ast.metadata.MetadataPackage; import com.intellij.psi.PsiElement; +import kotlin.Function1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.descriptors.CallableDescriptor; import org.jetbrains.kotlin.diagnostics.DiagnosticSink; -import org.jetbrains.kotlin.js.inline.context.*; +import org.jetbrains.kotlin.diagnostics.Errors; +import org.jetbrains.kotlin.js.inline.context.FunctionContext; +import org.jetbrains.kotlin.js.inline.context.InliningContext; +import org.jetbrains.kotlin.js.inline.context.NamingContext; import org.jetbrains.kotlin.js.inline.util.ExpressionDecomposer; -import org.jetbrains.kotlin.js.resolve.diagnostics.ErrorsJs; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.resolve.inline.InlineStrategy; import java.util.*; -import kotlin.Function1; import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.canBeExpression; import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.getInlineableCallReplacement; @@ -235,8 +238,9 @@ public class JsInliner extends JsVisitorWithContextImpl { JsCallInfo callInfo = it.next(); PsiElement psiElement = MetadataPackage.getPsiElement(callInfo.call); - if (psiElement != null) { - trace.report(ErrorsJs.INLINE_CALL_CYCLE.on(psiElement)); + CallableDescriptor descriptor = MetadataPackage.getDescriptor(callInfo.call); + if (psiElement != null && descriptor != null) { + trace.report(Errors.INLINE_CALL_CYCLE.on(psiElement, descriptor)); } if (callInfo.containingFunction == calledFunction) {