diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmReflectionAPICallChecker.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmReflectionAPICallChecker.kt new file mode 100644 index 00000000000..bc8add4324a --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/JvmReflectionAPICallChecker.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2016 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.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.resolve.calls.checkers.AbstractReflectionApiCallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext +import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.NO_REFLECTION_IN_CLASS_PATH +import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies +import org.jetbrains.kotlin.storage.StorageManager +import org.jetbrains.kotlin.storage.getValue + +/** + * If there's no Kotlin reflection implementation found in the classpath, checks that there are no usages + * of reflection API which will fail at runtime. + */ +class JvmReflectionAPICallChecker(private val module: ModuleDescriptor, storageManager: StorageManager) : AbstractReflectionApiCallChecker(module, storageManager) { + override val isWholeReflectionApiAvailable by storageManager.createLazyValue { + module.findClassAcrossModuleDependencies(JvmAbi.REFLECTION_FACTORY_IMPL) != null + } + + override fun report(element: PsiElement, context: CallCheckerContext) { + context.trace.report(NO_REFLECTION_IN_CLASS_PATH.on(element)) + } +} 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 fb55163c5dc..c4a7faa53c5 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 @@ -79,7 +79,7 @@ object JvmPlatformConfigurator : PlatformConfigurator( platformToKotlinClassMap = JavaToKotlinClassMap.INSTANCE ) { override fun configureModuleComponents(container: StorageComponentContainer) { - container.useImpl() + container.useImpl() container.useImpl() container.useInstance(JavaSyntheticConstructorsProvider) container.useInstance(JvmTypeSpecificityComparator) diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ReflectionAPICallChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AbstractReflectionApiCallChecker.kt similarity index 52% rename from compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ReflectionAPICallChecker.kt rename to compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AbstractReflectionApiCallChecker.kt index 502e5f35e37..8a80a8d94ef 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/checkers/ReflectionAPICallChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/AbstractReflectionApiCallChecker.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2015 JetBrains s.r.o. + * Copyright 2010-2016 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. @@ -14,42 +14,37 @@ * limitations under the License. */ -package org.jetbrains.kotlin.resolve.jvm.checkers +package org.jetbrains.kotlin.resolve.calls.checkers import com.intellij.psi.PsiElement import org.jetbrains.kotlin.builtins.KOTLIN_REFLECT_FQ_NAME -import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.ReflectionTypes +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor -import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.resolve.DescriptorUtils -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.NO_REFLECTION_IN_CLASS_PATH -import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.storage.getValue import org.jetbrains.kotlin.util.OperatorNameConventions /** - * If there's no Kotlin reflection implementation found in the classpath, checks that there are no usages - * of reflection API which will fail at runtime. + * Checks that there are no usages of reflection API which will fail at runtime. */ -class ReflectionAPICallChecker(private val module: ModuleDescriptor, storageManager: StorageManager) : CallChecker { - private val isReflectionAvailable by storageManager.createLazyValue { - module.findClassAcrossModuleDependencies(JvmAbi.REFLECTION_FACTORY_IMPL) != null - } +abstract class AbstractReflectionApiCallChecker(private val module: ModuleDescriptor, storageManager: StorageManager) : CallChecker { + protected abstract val isWholeReflectionApiAvailable: Boolean + protected abstract fun report(element: PsiElement, context: CallCheckerContext) private val kPropertyClasses by storageManager.createLazyValue { val reflectionTypes = ReflectionTypes(module) setOf(reflectionTypes.kProperty0, reflectionTypes.kProperty1, reflectionTypes.kProperty2) } - override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { - if (isReflectionAvailable) return + private val ANY_MEMBER_NAMES = setOf("equals", "hashCode", "toString") + + final override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { + if (isWholeReflectionApiAvailable) return // Do not report the diagnostic on built-in sources if (isReflectionSource(reportOn)) return @@ -58,25 +53,25 @@ class ReflectionAPICallChecker(private val module: ModuleDescriptor, storageMana val containingClass = descriptor.containingDeclaration as? ClassDescriptor ?: return if (!ReflectionTypes.isReflectionClass(containingClass)) return - // Skip some symbols which are supposed to work fine without kotlin-reflect.jar: - // - 'name' on anything - // - 'invoke' on functions (or on anything else for that matter) - // - 'get'/'set' on properties - val name = descriptor.name - when { - name == OperatorNameConventions.INVOKE -> return - name.asString() == "name" -> return - (name.asString() == "get" || name.asString() == "set") && - kPropertyClasses.any { kProperty -> DescriptorUtils.isSubclass(containingClass, kProperty) } -> return + if (!isAllowedReflectionApi(descriptor, containingClass)) { + report(reportOn, context) } - - context.trace.report(NO_REFLECTION_IN_CLASS_PATH.on(reportOn)) } + protected open fun isAllowedReflectionApi(descriptor: CallableDescriptor, containingClass: ClassDescriptor): Boolean { + val name = descriptor.name + return name.asString() in ANY_MEMBER_NAMES || + name == OperatorNameConventions.INVOKE || + name.asString() == "name" || + (name.asString() == "get" || name.asString() == "set") && containingClass.isKPropertyClass() + } + + private fun ClassDescriptor.isKPropertyClass() = kPropertyClasses.any { kProperty -> DescriptorUtils.isSubclass(this, kProperty) } + private fun isReflectionSource(reportOn: PsiElement): Boolean { val file = reportOn.containingFile as? KtFile ?: return false val fqName = file.packageFqName.toUnsafe() - return fqName.startsWith(KotlinBuiltIns.BUILT_INS_PACKAGE_NAME) && - (fqName == KOTLIN_REFLECT_FQ_NAME.toUnsafe() || fqName.asString().startsWith(KOTLIN_REFLECT_FQ_NAME.asString() + ".")) + return fqName == KOTLIN_REFLECT_FQ_NAME.toUnsafe() || fqName.asString().startsWith(KOTLIN_REFLECT_FQ_NAME.asString() + ".") } } + diff --git a/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.kt b/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.kt index 30ad017adf2..9845d3ca0ba 100644 --- a/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.kt +++ b/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.kt @@ -9,7 +9,6 @@ fun n02() = Foo::func fun n03() = Foo::class fun n04(p: KProperty0) = p.get() fun n05(p: KMutableProperty0) = p.set("") -fun n06(p: KProperty0) = p.get() fun n07(p: KFunction) = p.name fun n08(p: KProperty1) = p.get("") fun n09(p: KProperty2) = p.get("", "") @@ -20,3 +19,28 @@ fun y01() = Foo::prop.getter fun y02() = Foo::class.members fun y03() = Foo::class.simpleName fun y04() = Foo::class.properties + +fun kclass(k: KClass<*>, kt: KClass) { + k.simpleName + k.qualifiedName + k.members + k.constructors + k.nestedClasses + k.objectInstance + k.typeParameters + k.supertypes + k.visibility + k.isFinal + k.isOpen + k.isAbstract + k.isSealed + k.isData + k.isInner + k.isCompanion + + k.annotations + + k == kt + k.hashCode() + k.toString() +} diff --git a/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.txt b/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.txt index 74bb197f156..e8d20df6fe8 100644 --- a/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.txt +++ b/compiler/testData/diagnostics/tests/reflection/noReflectionInClassPath.txt @@ -1,11 +1,11 @@ package +public fun kclass(/*0*/ k: kotlin.reflect.KClass<*>, /*1*/ kt: kotlin.reflect.KClass): kotlin.Unit public fun n01(): kotlin.reflect.KProperty1 public fun n02(): kotlin.reflect.KFunction1 public fun n03(): kotlin.reflect.KClass public fun n04(/*0*/ p: kotlin.reflect.KProperty0): kotlin.Int public fun n05(/*0*/ p: kotlin.reflect.KMutableProperty0): kotlin.Unit -public fun n06(/*0*/ p: kotlin.reflect.KProperty0): kotlin.Int public fun n07(/*0*/ p: kotlin.reflect.KFunction): kotlin.String public fun n08(/*0*/ p: kotlin.reflect.KProperty1): kotlin.Int public fun n09(/*0*/ p: kotlin.reflect.KProperty2): kotlin.Int diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/reflection/reflectionApi.kt b/compiler/testData/diagnostics/testsWithJsStdLib/reflection/reflectionApi.kt new file mode 100644 index 00000000000..802a9d1128a --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/reflection/reflectionApi.kt @@ -0,0 +1,50 @@ +// !DIAGNOSTICS: -UNUSED_EXPRESSION +import kotlin.reflect.* + +class Foo(val prop: Any) { + fun func() {} +} + +fun testSomeValidCases(p0: KProperty0, pm0: KMutableProperty0, f: KFunction, p1: KProperty1, p2: KProperty2) { + Foo::prop + Foo::func + Foo::class + p0.get() + p0.name + pm0.set("") + f.name + p1.get("") + p2.get("", "") + (Foo::func).invoke(Foo("")) + (Foo::func)(Foo("")) + + p0 == pm0 + p1.equals(p2) + p0.hashCode() + f.toString() +} + +fun kclass(k: KClass<*>, kt: KClass) { + k.simpleName + k.qualifiedName + k.members + k.constructors + k.nestedClasses + k.objectInstance + k.typeParameters + k.supertypes + k.visibility + k.isFinal + k.isOpen + k.isAbstract + k.isSealed + k.isData + k.isInner + k.isCompanion + + k.annotations + + k == kt + k.hashCode() + k.toString() +} diff --git a/compiler/testData/diagnostics/testsWithJsStdLib/reflection/reflectionApi.txt b/compiler/testData/diagnostics/testsWithJsStdLib/reflection/reflectionApi.txt new file mode 100644 index 00000000000..dcf3db0eb78 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithJsStdLib/reflection/reflectionApi.txt @@ -0,0 +1,13 @@ +package + +public fun kclass(/*0*/ k: kotlin.reflect.KClass<*>, /*1*/ kt: kotlin.reflect.KClass): kotlin.Unit +public fun testSomeValidCases(/*0*/ p0: kotlin.reflect.KProperty0, /*1*/ pm0: kotlin.reflect.KMutableProperty0, /*2*/ f: kotlin.reflect.KFunction, /*3*/ p1: kotlin.reflect.KProperty1, /*4*/ p2: kotlin.reflect.KProperty2): kotlin.Unit + +public final class Foo { + public constructor Foo(/*0*/ prop: kotlin.Any) + public final val prop: kotlin.Any + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final fun func(): kotlin.Unit + 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/DiagnosticsTestWithJsStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java index 7c1cfb84277..715b44ac6a7 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithJsStdLibGenerated.java @@ -730,4 +730,19 @@ public class DiagnosticsTestWithJsStdLibGenerated extends AbstractDiagnosticsTes } } } + + @TestMetadata("compiler/testData/diagnostics/testsWithJsStdLib/reflection") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Reflection extends AbstractDiagnosticsTestWithJsStdLib { + public void testAllFilesPresentInReflection() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithJsStdLib/reflection"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("reflectionApi.kt") + public void testReflectionApi() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/testsWithJsStdLib/reflection/reflectionApi.kt"); + doTest(fileName); + } + } } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt index 3d7ec8c03db..19544a72a03 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/JsPlatformConfigurator.kt @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap import org.jetbrains.kotlin.resolve.IdentifierChecker import org.jetbrains.kotlin.resolve.OverloadFilter import org.jetbrains.kotlin.resolve.PlatformConfigurator +import org.jetbrains.kotlin.js.resolve.diagnostics.JsReflectionAPICallChecker import org.jetbrains.kotlin.resolve.scopes.SyntheticConstructorsProvider import org.jetbrains.kotlin.resolve.scopes.SyntheticScopes import org.jetbrains.kotlin.types.DynamicTypesAllowed @@ -49,5 +50,6 @@ object JsPlatformConfigurator : PlatformConfigurator( container.useInstance(SyntheticConstructorsProvider.Empty) container.useInstance(JsTypeSpecificityComparator) container.useInstance(JsNameClashChecker()) + container.useImpl() } } diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsReflectionAPICallChecker.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsReflectionAPICallChecker.kt new file mode 100644 index 00000000000..735b5e12fa5 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/resolve/diagnostics/JsReflectionAPICallChecker.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2016 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.js.resolve.diagnostics + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.builtins.ReflectionTypes +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.diagnostics.Errors.UNSUPPORTED +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.calls.checkers.AbstractReflectionApiCallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext +import org.jetbrains.kotlin.storage.StorageManager +import org.jetbrains.kotlin.storage.getValue + +class JsReflectionAPICallChecker(private val module: ModuleDescriptor, storageManager: StorageManager) : AbstractReflectionApiCallChecker(module, storageManager) { + override val isWholeReflectionApiAvailable: Boolean + get() = false + + override fun report(element: PsiElement, context: CallCheckerContext) { + context.trace.report(UNSUPPORTED.on(element, "This reflection API is not supported yet in JavaScript")) + } + + private val kClass by storageManager.createLazyValue { ReflectionTypes(module).kClass } + + override fun isAllowedReflectionApi(descriptor: CallableDescriptor, containingClass: ClassDescriptor): Boolean = + super.isAllowedReflectionApi(descriptor, containingClass) || + DescriptorUtils.isSubclass(containingClass, kClass) && descriptor.name.asString() == "simpleName" +}