From 26136cd0fe516c7791ec49c7e2965b0d022c68e6 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Mon, 25 Sep 2017 14:00:26 +0300 Subject: [PATCH] Check constness of 'apiVersionIsAtLeast' arguments --- .../kotlin/codegen/ExpressionCodegen.java | 45 +---------- .../kotlin/codegen/JvmCodegenUtil.java | 4 +- .../ApiVersionIsAtLeastArgumentsChecker.kt | 64 ++++++++++++++++ .../diagnostics/DefaultErrorMessagesJvm.java | 2 + .../resolve/jvm/diagnostics/ErrorsJvm.java | 2 + .../kotlin/resolve/jvm/jvmConstants.kt | 76 +++++++++++++++++++ .../jvm/platform/JvmPlatformConfigurator.kt | 3 +- .../apiVersionIsAtLeastHasConstArguments.kt | 23 ++++++ .../DiagnosticsTestWithStdLibGenerated.java | 15 ++++ ...ticsTestWithStdLibUsingJavacGenerated.java | 15 ++++ 10 files changed, 203 insertions(+), 46 deletions(-) create mode 100644 compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ApiVersionIsAtLeastArgumentsChecker.kt create mode 100644 compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/jvmConstants.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions/apiVersionIsAtLeastHasConstArguments.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index dbe4cd3d150..52a4908f839 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -80,6 +80,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.resolve.jvm.AsmTypes; import org.jetbrains.kotlin.resolve.jvm.JvmBindingContextSlices; +import org.jetbrains.kotlin.resolve.jvm.JvmConstantsKt; import org.jetbrains.kotlin.resolve.jvm.RuntimeAssertionInfo; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt; import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind; @@ -786,49 +787,7 @@ public class ExpressionCodegen extends KtVisitor impleme boolean takeUpConstValsAsConst, boolean shouldInlineConstVals ) { - CompileTimeConstant compileTimeValue = ConstantExpressionEvaluator.getConstant(expression, bindingContext); - if (compileTimeValue == null || compileTimeValue.getUsesNonConstValAsConstant()) { - return null; - } - - if (!shouldInlineConstVals && !takeUpConstValsAsConst && compileTimeValue.getUsesVariableAsConstant()) { - Ref containsNonInlinedVals = new Ref<>(false); - KtVisitor constantChecker = new KtVisitor() { - @Override - public Object visitSimpleNameExpression(@NotNull KtSimpleNameExpression expression, Object data) { - ResolvedCall resolvedCall = CallUtilKt.getResolvedCall(expression, bindingContext); - if (resolvedCall != null) { - CallableDescriptor callableDescriptor = resolvedCall.getResultingDescriptor(); - if (callableDescriptor instanceof PropertyDescriptor && - !JvmCodegenUtil.isInlinedJavaConstProperty((VariableDescriptor) callableDescriptor)) { - containsNonInlinedVals.set(true); - } - } - return null; - } - - @Override - public Object visitKtElement(@NotNull KtElement element, Object data) { - if (!containsNonInlinedVals.get()) { - element.acceptChildren(this); - } - return null; - } - }; - - expression.accept(constantChecker); - - if (containsNonInlinedVals.get()) { - return null; - } - } - - KotlinType expectedType = bindingContext.getType(expression); - if (expectedType == null) { - return null; - } - - return compileTimeValue.toConstantValue(expectedType); + return JvmConstantsKt.getCompileTimeConstant(expression, bindingContext, takeUpConstValsAsConst, shouldInlineConstVals); } @Override diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java index e8ff940e976..4e067f54f08 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java @@ -42,6 +42,7 @@ import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.inline.InlineUtil; +import org.jetbrains.kotlin.resolve.jvm.JvmConstantsKt; import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue; import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor; import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor; @@ -294,8 +295,7 @@ public class JvmCodegenUtil { } public static boolean isInlinedJavaConstProperty(VariableDescriptor descriptor) { - if (!(descriptor instanceof JavaPropertyDescriptor)) return false; - return descriptor.isConst(); + return JvmConstantsKt.isInlinedJavaConstProperty(descriptor); } @Nullable diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ApiVersionIsAtLeastArgumentsChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ApiVersionIsAtLeastArgumentsChecker.kt new file mode 100644 index 00000000000..8c9d27c534d --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ApiVersionIsAtLeastArgumentsChecker.kt @@ -0,0 +1,64 @@ +/* + * 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. + */ + +package org.jetbrains.kotlin.resolve.jvm.checkers + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor +import org.jetbrains.kotlin.psi.KtPsiUtil +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm +import org.jetbrains.kotlin.resolve.jvm.getCompileTimeConstant + +object ApiVersionIsAtLeastArgumentsChecker : CallChecker { + override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { + if (!isApiVersionIsAtLeast(resolvedCall.resultingDescriptor)) return + + val bindingContext = context.trace.bindingContext + val shouldInlineConstVals = context.languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals) + + for ((_, resolvedValueArgument) in resolvedCall.valueArguments) { + for (valueArgument in resolvedValueArgument.arguments) { + val ktExpression = KtPsiUtil.deparenthesize(valueArgument.getArgumentExpression() ?: continue) ?: continue + + val constant = getCompileTimeConstant(ktExpression, bindingContext, false, shouldInlineConstVals) + if (constant == null) { + context.trace.report(ErrorsJvm.API_VERSION_IS_AT_LEAST_ARGUMENT_SHOULD_BE_CONSTANT.on(ktExpression)) + } + } + } + } + + private fun isApiVersionIsAtLeast(descriptor: CallableDescriptor): Boolean { + val functionDescriptor = descriptor as? FunctionDescriptor ?: return false + + if (functionDescriptor.name.asString() != "apiVersionIsAtLeast") return false + + val returnType = functionDescriptor.returnType ?: return false + if (!KotlinBuiltIns.isBoolean(returnType)) return false + + if (!functionDescriptor.valueParameters.all { KotlinBuiltIns.isInt(it.type) }) return false + + val containingPackage = functionDescriptor.containingDeclaration as? PackageFragmentDescriptor ?: return false + return containingPackage.fqName.asString() == "kotlin.internal" + } +} \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java index 4cf269a0030..2a53007bbc3 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/DefaultErrorMessagesJvm.java @@ -123,6 +123,8 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension { MAP.put(JAVA_MODULE_DOES_NOT_DEPEND_ON_MODULE, "Symbol is declared in module ''{0}'' which current module does not depend on", STRING); MAP.put(JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE, "Symbol is declared in unnamed module which is not read by current module"); MAP.put(JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE, "Symbol is declared in module ''{0}'' which does not export package ''{1}''", STRING, STRING); + + MAP.put(API_VERSION_IS_AT_LEAST_ARGUMENT_SHOULD_BE_CONSTANT, "'apiVersionIsAtLeast' argument should be a constant expression"); } @NotNull diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java index 7d6c2c9840a..8e45318e108 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/diagnostics/ErrorsJvm.java @@ -105,6 +105,8 @@ public interface ErrorsJvm { DiagnosticFactory0 JAVA_MODULE_DOES_NOT_READ_UNNAMED_MODULE = DiagnosticFactory0.create(ERROR); DiagnosticFactory2 JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE = DiagnosticFactory2.create(ERROR); + DiagnosticFactory0 API_VERSION_IS_AT_LEAST_ARGUMENT_SHOULD_BE_CONSTANT = DiagnosticFactory0.create(WARNING); + enum NullabilityInformationSource { KOTLIN { @NotNull diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/jvmConstants.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/jvmConstants.kt new file mode 100644 index 00000000000..cfe144921d8 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/jvmConstants.kt @@ -0,0 +1,76 @@ +/* + * 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. + */ + +package org.jetbrains.kotlin.resolve.jvm + +import org.jetbrains.kotlin.descriptors.PropertyDescriptor +import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtVisitorVoid +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.constants.ConstantValue +import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator + +fun getCompileTimeConstant( + expression: KtExpression, + bindingContext: BindingContext, + takeUpConstValsAsConst: Boolean, + shouldInlineConstVals: Boolean +): ConstantValue<*>? { + val compileTimeValue = ConstantExpressionEvaluator.getConstant(expression, bindingContext) + if (compileTimeValue == null || compileTimeValue.usesNonConstValAsConstant) { + return null + } + + if (!shouldInlineConstVals && !takeUpConstValsAsConst && compileTimeValue.usesVariableAsConstant) { + val constantChecker = ConstantsChecker(bindingContext) + expression.accept(constantChecker) + if (constantChecker.containsNonInlinedVals) return null + } + + val expectedType = bindingContext.getType(expression) ?: return null + + return compileTimeValue.toConstantValue(expectedType) +} + + +private class ConstantsChecker(private val bindingContext: BindingContext) : KtVisitorVoid() { + var containsNonInlinedVals = false + + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + val resolvedCall = expression.getResolvedCall(bindingContext) + if (resolvedCall != null) { + val callableDescriptor = resolvedCall.resultingDescriptor + if (callableDescriptor is PropertyDescriptor && isInlinedJavaConstProperty(callableDescriptor as VariableDescriptor)) { + containsNonInlinedVals = true + } + } + } + + override fun visitKtElement(element: KtElement) { + if (!containsNonInlinedVals) { + element.acceptChildren(this) + } + } +} + + +fun isInlinedJavaConstProperty(descriptor: VariableDescriptor): Boolean = + (descriptor as? JavaPropertyDescriptor)?.isConst ?: false \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt index 34cb7760da0..8b22181c114 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/platform/JvmPlatformConfigurator.kt @@ -54,7 +54,8 @@ object JvmPlatformConfigurator : PlatformConfigurator( SuperCallWithDefaultArgumentsChecker(), ProtectedSyntheticExtensionCallChecker, ReifiedTypeParameterSubstitutionChecker(), - RuntimeAssertionsOnExtensionReceiverCallChecker + RuntimeAssertionsOnExtensionReceiverCallChecker, + ApiVersionIsAtLeastArgumentsChecker ), additionalTypeCheckers = listOf( diff --git a/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions/apiVersionIsAtLeastHasConstArguments.kt b/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions/apiVersionIsAtLeastHasConstArguments.kt new file mode 100644 index 00000000000..7050543ef96 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions/apiVersionIsAtLeastHasConstArguments.kt @@ -0,0 +1,23 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER +// SKIP_TXT +// FILE: test.kt +package test + +import kotlin.internal.* + +const val ZERO = 0 +const val ONE = 1 + +fun zero() = 0 +val one = 1 + +val test0 = apiVersionIsAtLeast(0, 0, 0) +val testConstVals = apiVersionIsAtLeast(ONE, ONE, ZERO) +val testConstExprs = apiVersionIsAtLeast(ONE + 0, 1 + 0, ((0 + 1 + 0))) +val testNonConstExprs = apiVersionIsAtLeast(one, zero(), one + 1) + +// FILE: apiVersionIsAtLeast.kt +package kotlin.internal + +fun apiVersionIsAtLeast(epic: Int, major: Int, minor: Int): Boolean = + false \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java index 42de2143933..53c109a5af8 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java @@ -539,6 +539,21 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW } } + @TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JvmSpecialFunctions extends AbstractDiagnosticsTestWithStdLib { + public void testAllFilesPresentInJvmSpecialFunctions() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("apiVersionIsAtLeastHasConstArguments.kt") + public void testApiVersionIsAtLeastHasConstArguments() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions/apiVersionIsAtLeastHasConstArguments.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmStatic") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java index e8eb40d96a3..ddeb90b278b 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java @@ -539,6 +539,21 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno } } + @TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JvmSpecialFunctions extends AbstractDiagnosticsTestWithStdLibUsingJavac { + public void testAllFilesPresentInJvmSpecialFunctions() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("apiVersionIsAtLeastHasConstArguments.kt") + public void testApiVersionIsAtLeastHasConstArguments() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmSpecialFunctions/apiVersionIsAtLeastHasConstArguments.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/diagnostics/testsWithStdLib/annotations/jvmStatic") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)