Prohibit non-const expressions through varargs in annotations

Fixes #KT-23153 for Kotlin 1.3

 The problem was in the type check of expression type against expected type. When feature `AssigningArraysToVarargsInNamedFormInAnnotations` (KT-20171) appeared, expected type could be wrong, which led to failed type check
This commit is contained in:
Mikhail Zarechenskiy
2018-03-12 02:10:25 +03:00
parent 4ebd11a7ae
commit 6086cd2cf4
12 changed files with 209 additions and 60 deletions
@@ -224,6 +224,7 @@ public interface Errors {
DiagnosticFactory0<KtExpression> ANNOTATION_PARAMETER_DEFAULT_VALUE_MUST_BE_CONSTANT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtAnnotatedExpression> ANNOTATIONS_ON_BLOCK_LEVEL_EXPRESSION_ON_THE_SAME_LINE = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<KtAnnotationEntry> ANNOTATION_USED_AS_ANNOTATION_ARGUMENT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtExpression> ANNOTATION_ARGUMENT_IS_NON_CONST = DiagnosticFactory0.create(WARNING);
DiagnosticFactory1<PsiElement, FqName> ILLEGAL_KOTLIN_VERSION_STRING_VALUE = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> NEWER_VERSION_IN_SINCE_KOTLIN = DiagnosticFactory1.create(WARNING);
@@ -828,6 +828,7 @@ public class DefaultErrorMessages {
"Use new line if whole block-level expression must be annotated or wrap annotated expression in parentheses");
MAP.put(ANNOTATION_USED_AS_ANNOTATION_ARGUMENT, "An annotation can't be used as the annotations argument");
MAP.put(ANNOTATION_ARGUMENT_IS_NON_CONST, "An annotation argument must be a compile-time constant");
MAP.put(CONST_VAL_NOT_TOP_LEVEL_OR_OBJECT, "Const 'val' are only allowed on top level or in objects");
MAP.put(CONST_VAL_WITH_DELEGATE, "Const 'val' should not have a delegate");
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2017 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.
* Copyright 2000-2018 JetBrains s.r.o. 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.resolve.calls.callResolverUtil
@@ -22,11 +11,13 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.ReflectionTypes
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor
import org.jetbrains.kotlin.lexer.KtToken
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.CallTransformer
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentForExpression
@@ -36,6 +27,7 @@ import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystem
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.EXPECTED_TYPE_POSITION
import org.jetbrains.kotlin.resolve.calls.inference.getNestedTypeVariables
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate
import org.jetbrains.kotlin.resolve.descriptorUtil.isParameterOfAnnotation
@@ -186,41 +178,65 @@ fun getSuperCallExpression(call: Call): KtSuperExpression? {
fun getEffectiveExpectedType(
parameterDescriptor: ValueParameterDescriptor,
argument: ValueArgument,
context: ResolutionContext<*>
resolvedArgument: ResolvedValueArgument,
languageVersionSettings: LanguageVersionSettings,
trace: BindingTrace
): KotlinType {
if (argument.getSpreadElement() != null || shouldCheckAsArray(parameterDescriptor, argument, context)) {
if (parameterDescriptor.varargElementType == null) {
// Spread argument passed to a non-vararg parameter, an error is already reported by ValueArgumentsToParametersMapper
return DONT_CARE
}
return parameterDescriptor.type
}
val varargElementType = parameterDescriptor.varargElementType
if (varargElementType != null) {
return varargElementType
}
return parameterDescriptor.type
val argument = resolvedArgument.arguments.singleOrNull()
return if (argument != null)
getEffectiveExpectedTypeForSingleArgument(parameterDescriptor, argument, languageVersionSettings, trace)
else
getExpectedType(parameterDescriptor)
}
private fun shouldCheckAsArray(
fun getEffectiveExpectedType(
parameterDescriptor: ValueParameterDescriptor,
argument: ValueArgument,
context: ResolutionContext<*>
): KotlinType {
return getEffectiveExpectedTypeForSingleArgument(parameterDescriptor, argument, context.languageVersionSettings, context.trace)
}
fun getEffectiveExpectedTypeForSingleArgument(
parameterDescriptor: ValueParameterDescriptor,
argument: ValueArgument,
languageVersionSettings: LanguageVersionSettings,
trace: BindingTrace
): KotlinType {
if (argument.getSpreadElement() != null) {
// Spread argument passed to a non-vararg parameter, an error is already reported by ValueArgumentsToParametersMapper
return if (parameterDescriptor.varargElementType == null) DONT_CARE else parameterDescriptor.type
}
if (arrayAssignmentToVarargInNamedFormInAnnotation(parameterDescriptor, argument, languageVersionSettings, trace)) {
return parameterDescriptor.type
}
return getExpectedType(parameterDescriptor)
}
private fun getExpectedType(parameterDescriptor: ValueParameterDescriptor): KotlinType {
return parameterDescriptor.varargElementType ?: parameterDescriptor.type
}
private fun arrayAssignmentToVarargInNamedFormInAnnotation(
parameterDescriptor: ValueParameterDescriptor,
argument: ValueArgument,
languageVersionSettings: LanguageVersionSettings,
trace: BindingTrace
): Boolean {
if (!context.languageVersionSettings.supportsFeature(LanguageFeature.AssigningArraysToVarargsInNamedFormInAnnotations)) return false
if (!languageVersionSettings.supportsFeature(LanguageFeature.AssigningArraysToVarargsInNamedFormInAnnotations)) return false
if (!isParameterOfAnnotation(parameterDescriptor)) return false
return argument.isNamed() && parameterDescriptor.isVararg && isArrayOrArrayLiteral(argument, context)
return argument.isNamed() && parameterDescriptor.isVararg && isArrayOrArrayLiteral(argument, trace)
}
fun isArrayOrArrayLiteral(argument: ValueArgument, context: ResolutionContext<*>): Boolean {
fun isArrayOrArrayLiteral(argument: ValueArgument, trace: BindingTrace): Boolean {
val argumentExpression = argument.getArgumentExpression() ?: return false
if (argumentExpression is KtCollectionLiteralExpression) return true
val type = context.trace.getType(argumentExpression) ?: return false
val type = trace.getType(argumentExpression) ?: return false
return KotlinBuiltIns.isArrayOrPrimitiveArray(type)
}
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2017 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.
* Copyright 2000-2018 JetBrains s.r.o. 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.resolve.calls.checkers
@@ -61,7 +50,7 @@ class AssigningNamedArgumentToVarargChecker : CallChecker {
argumentExpression: KtExpression,
context: ResolutionContext<*>
) {
if (isArrayOrArrayLiteral(argument, context)) {
if (isArrayOrArrayLiteral(argument, context.trace)) {
if (argument.hasSpread()) {
context.trace.report(Errors.ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_ANNOTATION.on(argumentExpression))
}
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.BindingContext.COLLECTION_LITERAL_CALL
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.getEffectiveExpectedType
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
@@ -86,8 +87,9 @@ class ConstantExpressionEvaluator(
val varargElementType = parameterDescriptor.varargElementType
val argumentsAsVararg = varargElementType != null && !hasSpread(resolvedArgument)
val constantType = if (argumentsAsVararg) varargElementType else parameterDescriptor.type
val compileTimeConstants = resolveAnnotationValueArguments(resolvedArgument, constantType!!, trace)
val constants = compileTimeConstants.map { it.toConstantValue(constantType) }
val expectedType = getEffectiveExpectedType(parameterDescriptor, resolvedArgument, languageVersionSettings, trace)
val compileTimeConstants = resolveAnnotationValueArguments(resolvedArgument, constantType!!, expectedType, trace)
val constants = compileTimeConstants.map { it.toConstantValue(expectedType) }
if (argumentsAsVararg) {
if (isArrayPassedInNamedForm(constants, resolvedArgument)) return constants.single()
@@ -110,11 +112,12 @@ class ConstantExpressionEvaluator(
private fun checkCompileTimeConstant(
argumentExpression: KtExpression,
expressionType: KotlinType,
trace: BindingTrace
trace: BindingTrace,
useDeprecationWarning: Boolean
) {
val constant = ConstantExpressionEvaluator.getConstant(argumentExpression, trace.bindingContext)
if (constant != null && constant.canBeUsedInAnnotations) {
checkInnerPartsOfCompileTimeConstant(constant, trace, argumentExpression)
checkInnerPartsOfCompileTimeConstant(constant, trace, argumentExpression, useDeprecationWarning)
return
}
@@ -125,13 +128,17 @@ class ConstantExpressionEvaluator(
else -> Errors.ANNOTATION_ARGUMENT_MUST_BE_CONST
}
trace.report(diagnosticFactory.on(argumentExpression))
if (useDeprecationWarning)
reportDeprecationWarningOnNonConst(argumentExpression, trace)
else
trace.report(diagnosticFactory.on(argumentExpression))
}
private fun checkInnerPartsOfCompileTimeConstant(
constant: CompileTimeConstant<*>,
trace: BindingTrace,
argumentExpression: KtExpression
argumentExpression: KtExpression,
useDeprecationWarning: Boolean
) {
// array(1, <!>null<!>, 3) - error should be reported on inner expression
val callArguments = when (argumentExpression) {
@@ -143,12 +150,17 @@ class ConstantExpressionEvaluator(
if (callArguments != null) {
for (argument in callArguments) {
val type = trace.getType(argument) ?: continue
checkCompileTimeConstant(argument, type, trace)
checkCompileTimeConstant(argument, type, trace, useDeprecationWarning)
}
}
if (constant.usesNonConstValAsConstant) {
trace.report(Errors.NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION.on(argumentExpression))
if (useDeprecationWarning) {
reportDeprecationWarningOnNonConst(argumentExpression, trace)
} else {
trace.report(Errors.NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION.on(argumentExpression))
}
}
if (argumentExpression is KtClassLiteralExpression) {
@@ -156,12 +168,20 @@ class ConstantExpressionEvaluator(
if (lhsExpression != null) {
val doubleColonLhs = trace.bindingContext.get(BindingContext.DOUBLE_COLON_LHS, lhsExpression)
if (doubleColonLhs is DoubleColonLHS.Expression && !doubleColonLhs.isObjectQualifier) {
trace.report(Errors.ANNOTATION_ARGUMENT_MUST_BE_KCLASS_LITERAL.on(argumentExpression))
if (useDeprecationWarning) {
reportDeprecationWarningOnNonConst(argumentExpression, trace)
} else {
trace.report(Errors.ANNOTATION_ARGUMENT_MUST_BE_KCLASS_LITERAL.on(argumentExpression))
}
}
}
}
}
private fun reportDeprecationWarningOnNonConst(expression: KtExpression, trace: BindingTrace) {
trace.report(Errors.ANNOTATION_ARGUMENT_IS_NON_CONST.on(expression))
}
private fun getArgumentExpressionsForArrayCall(
expression: KtCallExpression,
trace: BindingTrace
@@ -203,6 +223,7 @@ class ConstantExpressionEvaluator(
private fun resolveAnnotationValueArguments(
resolvedValueArgument: ResolvedValueArgument,
deprecatedExpectedType: KotlinType,
expectedType: KotlinType,
trace: BindingTrace
): List<CompileTimeConstant<*>> {
@@ -220,12 +241,18 @@ class ConstantExpressionEvaluator(
val expressionType = trace.getType(argumentExpression) ?: continue
if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(expressionType, expectedType)) {
// TYPE_MISMATCH should be reported otherwise
continue
// this type check should not used as it can introduce subtle bugs when type checking rules against expected type are changing
if (!languageVersionSettings.supportsFeature(LanguageFeature.ProhibitNonConstValuesAsVarargsInAnnotations) &&
!KotlinTypeChecker.DEFAULT.isSubtypeOf(expressionType, deprecatedExpectedType)
) {
if (KotlinTypeChecker.DEFAULT.isSubtypeOf(expressionType, expectedType)) {
checkCompileTimeConstant(argumentExpression, expressionType, trace, useDeprecationWarning = true)
}
continue // TYPE_MISMATCH should be reported otherwise
}
checkCompileTimeConstant(argumentExpression, expressionType, trace)
checkCompileTimeConstant(argumentExpression, expressionType, trace, useDeprecationWarning = false)
}
return constants
}
@@ -0,0 +1,26 @@
// !LANGUAGE: -ProhibitNonConstValuesAsVarargsInAnnotations
val nonConstArray = longArrayOf(0)
fun nonConstFun(): LongArray = TODO()
fun nonConstLong(): Long = TODO()
annotation class Anno(vararg val value: Long)
@Anno(value = <!ANNOTATION_ARGUMENT_IS_NON_CONST!>nonConstArray<!>)
fun foo1() {}
@Anno(value = <!ANNOTATION_ARGUMENT_IS_NON_CONST!>nonConstFun()<!>)
fun foo2() {}
@Anno(value = <!ANNOTATION_ARGUMENT_IS_NON_CONST!>longArrayOf(<!ANNOTATION_ARGUMENT_IS_NON_CONST!>nonConstLong()<!>)<!>)
fun foo3() {}
@Anno(value = <!ANNOTATION_ARGUMENT_IS_NON_CONST!>[<!ANNOTATION_ARGUMENT_IS_NON_CONST!>nonConstLong()<!>]<!>)
fun foo4() {}
@Anno(value = *<!ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_ANNOTATION, ANNOTATION_ARGUMENT_MUST_BE_CONST!>nonConstArray<!>)
fun bar1() {}
@Anno(*<!ANNOTATION_ARGUMENT_MUST_BE_CONST!>nonConstArray<!>)
fun bar2() {}
@@ -0,0 +1,19 @@
package
public val nonConstArray: kotlin.LongArray
@Anno public fun bar1(): kotlin.Unit
@Anno public fun bar2(): kotlin.Unit
@Anno(value = {}) public fun foo1(): kotlin.Unit
@Anno(value = {}) public fun foo2(): kotlin.Unit
@Anno(value = {}) public fun foo3(): kotlin.Unit
@Anno(value = {}) public fun foo4(): kotlin.Unit
public fun nonConstFun(): kotlin.LongArray
public fun nonConstLong(): kotlin.Long
public final annotation class Anno : kotlin.Annotation {
public constructor Anno(/*0*/ vararg value: kotlin.Long /*kotlin.LongArray*/)
public final val value: kotlin.LongArray
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
}
@@ -0,0 +1,26 @@
// !LANGUAGE: +ProhibitNonConstValuesAsVarargsInAnnotations
val nonConstArray = longArrayOf(0)
fun nonConstFun(): LongArray = TODO()
fun nonConstLong(): Long = TODO()
annotation class Anno(vararg val value: Long)
@Anno(value = <!ANNOTATION_ARGUMENT_MUST_BE_CONST!>nonConstArray<!>)
fun foo1() {}
@Anno(value = <!ANNOTATION_ARGUMENT_MUST_BE_CONST!>nonConstFun()<!>)
fun foo2() {}
@Anno(value = <!NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION!>longArrayOf(<!ANNOTATION_ARGUMENT_MUST_BE_CONST!>nonConstLong()<!>)<!>)
fun foo3() {}
@Anno(value = <!NON_CONST_VAL_USED_IN_CONSTANT_EXPRESSION!>[<!ANNOTATION_ARGUMENT_MUST_BE_CONST!>nonConstLong()<!>]<!>)
fun foo4() {}
@Anno(value = *<!ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_ANNOTATION, ANNOTATION_ARGUMENT_MUST_BE_CONST!>nonConstArray<!>)
fun bar1() {}
@Anno(*<!ANNOTATION_ARGUMENT_MUST_BE_CONST!>nonConstArray<!>)
fun bar2() {}
@@ -0,0 +1,19 @@
package
public val nonConstArray: kotlin.LongArray
@Anno public fun bar1(): kotlin.Unit
@Anno public fun bar2(): kotlin.Unit
@Anno(value = {}) public fun foo1(): kotlin.Unit
@Anno(value = {}) public fun foo2(): kotlin.Unit
@Anno(value = {}) public fun foo3(): kotlin.Unit
@Anno(value = {}) public fun foo4(): kotlin.Unit
public fun nonConstFun(): kotlin.LongArray
public fun nonConstLong(): kotlin.Long
public final annotation class Anno : kotlin.Annotation {
public constructor Anno(/*0*/ vararg value: kotlin.Long /*kotlin.LongArray*/)
public final val value: kotlin.LongArray
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
}
@@ -24536,6 +24536,18 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("assignNonConstSingleArrayElementAsVarargInAnnotation.kt")
public void testAssignNonConstSingleArrayElementAsVarargInAnnotation() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/varargs/assignNonConstSingleArrayElementAsVarargInAnnotation.kt");
doTest(fileName);
}
@TestMetadata("assignNonConstSingleArrayElementAsVarargInAnnotationError.kt")
public void testAssignNonConstSingleArrayElementAsVarargInAnnotationError() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/varargs/assignNonConstSingleArrayElementAsVarargInAnnotationError.kt");
doTest(fileName);
}
@TestMetadata("assigningArraysToVarargsInAnnotations.kt")
public void testAssigningArraysToVarargsInAnnotations() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/varargs/assigningArraysToVarargsInAnnotations.kt");
@@ -24536,6 +24536,18 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
doTest(fileName);
}
@TestMetadata("assignNonConstSingleArrayElementAsVarargInAnnotation.kt")
public void testAssignNonConstSingleArrayElementAsVarargInAnnotation() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/varargs/assignNonConstSingleArrayElementAsVarargInAnnotation.kt");
doTest(fileName);
}
@TestMetadata("assignNonConstSingleArrayElementAsVarargInAnnotationError.kt")
public void testAssignNonConstSingleArrayElementAsVarargInAnnotationError() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/varargs/assignNonConstSingleArrayElementAsVarargInAnnotationError.kt");
doTest(fileName);
}
@TestMetadata("assigningArraysToVarargsInAnnotations.kt")
public void testAssigningArraysToVarargsInAnnotations() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/varargs/assigningArraysToVarargsInAnnotations.kt");
@@ -67,6 +67,7 @@ enum class LanguageFeature(
JvmStaticInInterface(KOTLIN_1_3),
InlineClasses(KOTLIN_1_3),
ProhibitVisibilityOfNestedClassifiersFromSupertypesOfCompanion(KOTLIN_1_3),
ProhibitNonConstValuesAsVarargsInAnnotations(KOTLIN_1_3),
StrictJavaNullabilityAssertions(sinceVersion = null, defaultState = State.DISABLED),
ProperIeee754Comparisons(sinceVersion = null, defaultState = State.DISABLED),