From 6fb913a463eb00e7d2e24f2817c0ed2e54622fbf Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Tue, 24 Jul 2018 13:41:37 +0300 Subject: [PATCH] KT-22274 report error/warning on incorrect return target label --- .../cfg/ControlFlowInformationProvider.kt | 2 +- .../kotlin/cfg/ControlFlowProcessor.kt | 29 ++++++--- .../kotlin/cfg/pseudocode/PseudocodeUtil.java | 2 +- .../jetbrains/kotlin/diagnostics/Errors.java | 3 + .../rendering/DefaultErrorMessages.java | 3 + .../testData/cli/jvm/apiVersionInvalid.out | 2 +- .../cli/jvm/languageVersionInvalid.out | 2 +- .../notAFunctionLabel_after.kt | 62 +++++++++++++++++++ .../notAFunctionLabel_after.txt | 20 ++++++ .../notAFunctionLabel_before.kt | 62 +++++++++++++++++++ .../notAFunctionLabel_before.txt | 20 ++++++ .../checkers/DiagnosticsTestGenerated.java | 10 +++ .../DiagnosticsUsingJavacTestGenerated.java | 10 +++ .../kotlin/config/LanguageVersionSettings.kt | 5 +- .../kotlin/gradle/dsl/KotlinCommonOptions.kt | 4 +- 15 files changed, 222 insertions(+), 14 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.kt create mode 100644 compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.txt create mode 100644 compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.kt create mode 100644 compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt index 2061042728c..f0372fca8a9 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt @@ -70,7 +70,7 @@ class ControlFlowInformationProvider private constructor( ) : this( declaration, trace, - ControlFlowProcessor(trace).generatePseudocode(declaration), + ControlFlowProcessor(trace, languageVersionSettings).generatePseudocode(declaration), languageVersionSettings, diagnosticSuppressor ) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt index 655e5711bc9..7324ef2f1d2 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt @@ -30,11 +30,14 @@ import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeImpl import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.AccessTarget import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.contracts.description.InvocationKind import org.jetbrains.kotlin.contracts.description.canBeRevisited import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor +import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors.* import org.jetbrains.kotlin.lexer.KtToken import org.jetbrains.kotlin.lexer.KtTokens @@ -63,7 +66,10 @@ import java.util.* typealias DeferredGenerator = (ControlFlowBuilder) -> Unit -class ControlFlowProcessor(private val trace: BindingTrace) { +class ControlFlowProcessor( + private val trace: BindingTrace, + private val languageVersionSettings: LanguageVersionSettings? +) { private val builder: ControlFlowBuilder = ControlFlowInstructionsGenerator() @@ -927,12 +933,10 @@ class ControlFlowProcessor(private val trace: BindingTrace) { val subroutine: KtElement? val labelName = expression.getLabelName() subroutine = if (labelElement != null && labelName != null) { - val labeledElement = trace.get(BindingContext.LABEL_TARGET, labelElement) - if (labeledElement != null) { - assert(labeledElement is KtElement) - labeledElement as KtElement? - } else { - null + trace.get(BindingContext.LABEL_TARGET, labelElement)?.let { labeledElement -> + val labeledKtElement = labeledElement as KtElement + checkReturnLabelTarget(expression, labeledKtElement) + labeledKtElement } } else { builder.returnSubroutine @@ -951,6 +955,17 @@ class ControlFlowProcessor(private val trace: BindingTrace) { } } + private fun checkReturnLabelTarget(returnExpression: KtReturnExpression, labeledElement: KtElement) { + if (languageVersionSettings == null) return + if (labeledElement !is KtFunctionLiteral && labeledElement !is KtNamedFunction) { + if (languageVersionSettings.supportsFeature(LanguageFeature.RestrictReturnStatementTarget)) { + trace.report(Errors.NOT_A_FUNCTION_LABEL.on(returnExpression)) + } else { + trace.report(Errors.NOT_A_FUNCTION_LABEL_WARNING.on(returnExpression)) + } + } + } + override fun visitParameter(parameter: KtParameter) { builder.declareParameter(parameter) val defaultValue = parameter.defaultValue diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeUtil.java b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeUtil.java index ca28fa19b5c..2183d6246cc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeUtil.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeUtil.java @@ -89,7 +89,7 @@ public class PseudocodeUtil { return false; } }; - return new ControlFlowProcessor(mockTrace).generatePseudocode(declaration); + return new ControlFlowProcessor(mockTrace, null).generatePseudocode(declaration); } @Nullable diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 563630ad157..97344c48a0a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -819,6 +819,9 @@ public interface Errors { DiagnosticFactory0 BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY = DiagnosticFactory0.create(ERROR); DiagnosticFactory1 NOT_A_LOOP_LABEL = DiagnosticFactory1.create(ERROR); + DiagnosticFactory0 NOT_A_FUNCTION_LABEL = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 NOT_A_FUNCTION_LABEL_WARNING = DiagnosticFactory0.create(WARNING); + // Control flow / Data flow DiagnosticFactory1> UNREACHABLE_CODE = DiagnosticFactory1.create( 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 0f6b0a7b265..6f1982dea51 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -599,6 +599,9 @@ public class DefaultErrorMessages { MAP.put(BREAK_OR_CONTINUE_JUMPS_ACROSS_FUNCTION_BOUNDARY, "'break' or 'continue' jumps across a function or a class boundary"); MAP.put(NOT_A_LOOP_LABEL, "The label ''{0}'' does not denote a loop", STRING); + MAP.put(NOT_A_FUNCTION_LABEL, "Target label does not denote a function"); + MAP.put(NOT_A_FUNCTION_LABEL_WARNING, "Target label does not denote a function"); + MAP.put(ANONYMOUS_INITIALIZER_IN_INTERFACE, "Anonymous initializers are not allowed in interfaces"); MAP.put(NULLABLE_SUPERTYPE, "A supertype cannot be nullable"); MAP.put(DYNAMIC_SUPERTYPE, "A supertype cannot be dynamic"); diff --git a/compiler/testData/cli/jvm/apiVersionInvalid.out b/compiler/testData/cli/jvm/apiVersionInvalid.out index fb5faccc096..c866c879a5b 100644 --- a/compiler/testData/cli/jvm/apiVersionInvalid.out +++ b/compiler/testData/cli/jvm/apiVersionInvalid.out @@ -1,3 +1,3 @@ error: unknown API version: 239.42 -Supported API versions: 1.0, 1.1, 1.2, 1.3 (EXPERIMENTAL) +Supported API versions: 1.0, 1.1, 1.2, 1.3 (EXPERIMENTAL), 1.4 (EXPERIMENTAL) COMPILATION_ERROR diff --git a/compiler/testData/cli/jvm/languageVersionInvalid.out b/compiler/testData/cli/jvm/languageVersionInvalid.out index 6ed49ab92e7..d29a38a75e3 100644 --- a/compiler/testData/cli/jvm/languageVersionInvalid.out +++ b/compiler/testData/cli/jvm/languageVersionInvalid.out @@ -1,3 +1,3 @@ error: unknown language version: 239.42 -Supported language versions: 1.0, 1.1, 1.2, 1.3 (EXPERIMENTAL) +Supported language versions: 1.0, 1.1, 1.2, 1.3 (EXPERIMENTAL), 1.4 (EXPERIMENTAL) COMPILATION_ERROR diff --git a/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.kt b/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.kt new file mode 100644 index 00000000000..ba4ec6da0e4 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.kt @@ -0,0 +1,62 @@ +// !LANGUAGE: +RestrictReturnStatementTarget + +@Target(AnnotationTarget.EXPRESSION) +@Retention(AnnotationRetention.SOURCE) +annotation class Ann + +fun testFunctionName() { + return@testFunctionName +} + +fun testHighOrderFunctionName() { + run { + return@run + } +} + +fun testLambdaLabel() = + lambda@ { + return@lambda + } + +fun testParenthesizedLambdaLabel() = + lambda@ ( { + return@lambda + } ) + +fun testAnnotatedLambdaLabel() = + lambda@ @Ann { + return@lambda + } + +fun testLambdaMultipleLabels1() = + lambda1@ lambda2@ { + return@lambda1 + } + +fun testLambdaMultipleLabels2() = + lambda1@ lambda2@ { + return@lambda2 + } + +fun testAnonymousFunctionLabel() = + anonFun@ fun() { + return@anonFun + } + +fun testLoopLabelInReturn(xs: List) { + L@ for (x in xs) { + if (x > 0) return@L + } +} + +fun testValLabelInReturn() { + L@ val fn = { return@L } + fn() +} + +fun testHighOrderFunctionCallLabelInReturn() { + L@ run { + return@L + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.txt b/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.txt new file mode 100644 index 00000000000..30db1481b1f --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.txt @@ -0,0 +1,20 @@ +package + +public fun testAnnotatedLambdaLabel(): () -> kotlin.Unit +public fun testAnonymousFunctionLabel(): () -> kotlin.Unit +public fun testFunctionName(): kotlin.Unit +public fun testHighOrderFunctionCallLabelInReturn(): kotlin.Unit +public fun testHighOrderFunctionName(): kotlin.Unit +public fun testLambdaLabel(): () -> kotlin.Unit +public fun testLambdaMultipleLabels1(): () -> kotlin.Unit +public fun testLambdaMultipleLabels2(): () -> kotlin.Unit +public fun testLoopLabelInReturn(/*0*/ xs: kotlin.collections.List): kotlin.Unit +public fun testParenthesizedLambdaLabel(): () -> kotlin.Unit +public fun testValLabelInReturn(): kotlin.Unit + +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.EXPRESSION}) @kotlin.annotation.Retention(value = AnnotationRetention.SOURCE) public final annotation class Ann : kotlin.Annotation { + public constructor Ann() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.kt b/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.kt new file mode 100644 index 00000000000..84714135a02 --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.kt @@ -0,0 +1,62 @@ +// !LANGUAGE: -RestrictReturnStatementTarget + +@Target(AnnotationTarget.EXPRESSION) +@Retention(AnnotationRetention.SOURCE) +annotation class Ann + +fun testFunctionName() { + return@testFunctionName +} + +fun testHighOrderFunctionName() { + run { + return@run + } +} + +fun testLambdaLabel() = + lambda@ { + return@lambda + } + +fun testParenthesizedLambdaLabel() = + lambda@ ( { + return@lambda + } ) + +fun testAnnotatedLambdaLabel() = + lambda@ @Ann { + return@lambda + } + +fun testLambdaMultipleLabels1() = + lambda1@ lambda2@ { + return@lambda1 + } + +fun testLambdaMultipleLabels2() = + lambda1@ lambda2@ { + return@lambda2 + } + +fun testAnonymousFunctionLabel() = + anonFun@ fun() { + return@anonFun + } + +fun testLoopLabelInReturn(xs: List) { + L@ for (x in xs) { + if (x > 0) return@L + } +} + +fun testValLabelInReturn() { + L@ val fn = { return@L } + fn() +} + +fun testHighOrderFunctionCallLabelInReturn() { + L@ run { + return@L + } +} \ No newline at end of file diff --git a/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.txt b/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.txt new file mode 100644 index 00000000000..30db1481b1f --- /dev/null +++ b/compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.txt @@ -0,0 +1,20 @@ +package + +public fun testAnnotatedLambdaLabel(): () -> kotlin.Unit +public fun testAnonymousFunctionLabel(): () -> kotlin.Unit +public fun testFunctionName(): kotlin.Unit +public fun testHighOrderFunctionCallLabelInReturn(): kotlin.Unit +public fun testHighOrderFunctionName(): kotlin.Unit +public fun testLambdaLabel(): () -> kotlin.Unit +public fun testLambdaMultipleLabels1(): () -> kotlin.Unit +public fun testLambdaMultipleLabels2(): () -> kotlin.Unit +public fun testLoopLabelInReturn(/*0*/ xs: kotlin.collections.List): kotlin.Unit +public fun testParenthesizedLambdaLabel(): () -> kotlin.Unit +public fun testValLabelInReturn(): kotlin.Unit + +@kotlin.annotation.Target(allowedTargets = {AnnotationTarget.EXPRESSION}) @kotlin.annotation.Retention(value = AnnotationRetention.SOURCE) public final annotation class Ann : kotlin.Annotation { + public constructor Ann() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index b754f1c1150..a87f8812a62 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -4130,6 +4130,16 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { runTest("compiler/testData/diagnostics/tests/controlStructures/localReturnInsidePropertyAccessor.kt"); } + @TestMetadata("notAFunctionLabel_after.kt") + public void testNotAFunctionLabel_after() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.kt"); + } + + @TestMetadata("notAFunctionLabel_before.kt") + public void testNotAFunctionLabel_before() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.kt"); + } + @TestMetadata("specialConstructsAndPlatformTypes.kt") public void testSpecialConstructsAndPlatformTypes() throws Exception { runTest("compiler/testData/diagnostics/tests/controlStructures/specialConstructsAndPlatformTypes.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java index b8c0c5ff8fe..d4fff9f78be 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsUsingJavacTestGenerated.java @@ -4130,6 +4130,16 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing runTest("compiler/testData/diagnostics/tests/controlStructures/localReturnInsidePropertyAccessor.kt"); } + @TestMetadata("notAFunctionLabel_after.kt") + public void testNotAFunctionLabel_after() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_after.kt"); + } + + @TestMetadata("notAFunctionLabel_before.kt") + public void testNotAFunctionLabel_before() throws Exception { + runTest("compiler/testData/diagnostics/tests/controlStructures/notAFunctionLabel_before.kt"); + } + @TestMetadata("specialConstructsAndPlatformTypes.kt") public void testSpecialConstructsAndPlatformTypes() throws Exception { runTest("compiler/testData/diagnostics/tests/controlStructures/specialConstructsAndPlatformTypes.kt"); diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index d354b196ccb..2d10fc58eb4 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -83,6 +83,8 @@ enum class LanguageFeature( RestrictRetentionForExpressionAnnotations(KOTLIN_1_3, kind = BUG_FIX), NoConstantValueAttributeForNonConstVals(KOTLIN_1_3, kind = BUG_FIX), + RestrictReturnStatementTarget(KOTLIN_1_4, kind = BUG_FIX), + StrictJavaNullabilityAssertions(sinceVersion = null, defaultState = State.DISABLED), ProperIeee754Comparisons(sinceVersion = null, defaultState = State.DISABLED, kind = BUG_FIX), @@ -185,7 +187,8 @@ enum class LanguageVersion(val major: Int, val minor: Int) : DescriptionAware { KOTLIN_1_0(1, 0), KOTLIN_1_1(1, 1), KOTLIN_1_2(1, 2), - KOTLIN_1_3(1, 3); + KOTLIN_1_3(1, 3), + KOTLIN_1_4(1, 4); val isStable: Boolean get() = this <= LATEST_STABLE diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinCommonOptions.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinCommonOptions.kt index bb3350f0507..f1a6cfdba5d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinCommonOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinCommonOptions.kt @@ -6,14 +6,14 @@ interface KotlinCommonOptions : org.jetbrains.kotlin.gradle.dsl.KotlinCommonToo /** * Allow to use declarations only from the specified version of bundled libraries - * Possible values: "1.0", "1.1", "1.2", "1.3 (EXPERIMENTAL)" + * Possible values: "1.0", "1.1", "1.2", "1.3 (EXPERIMENTAL)", "1.4 (EXPERIMENTAL)" * Default value: null */ var apiVersion: kotlin.String? /** * Provide source compatibility with specified language version - * Possible values: "1.0", "1.1", "1.2", "1.3 (EXPERIMENTAL)" + * Possible values: "1.0", "1.1", "1.2", "1.3 (EXPERIMENTAL)", "1.4 (EXPERIMENTAL)" * Default value: null */ var languageVersion: kotlin.String?