Make "reflection not found" a warning, provide a quick fix

Reporting the warning on each "::", as ReflectionNotFoundInspection did, is not
correct anymore, because for example name/get/set on properties works perfectly
without kotlin-reflect.jar in the classpath. So instead we report the warning
on calls to functions from reflection interfaces. This is not perfect either
because it's wrong in projects with custom implementations of reflection
interfaces, but this case is so rare that the users can suppress the warning
there anyway

 #KT-7176 Fixed
This commit is contained in:
Alexander Udalov
2015-07-02 17:39:16 +03:00
parent 4f1247f03f
commit 636b63a8c5
24 changed files with 240 additions and 261 deletions
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValue
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.calls.smartcasts.Nullability
import org.jetbrains.kotlin.resolve.jvm.calls.checkers.NeedSyntheticChecker
import org.jetbrains.kotlin.resolve.jvm.calls.checkers.ReflectionAPICallChecker
import org.jetbrains.kotlin.resolve.jvm.calls.checkers.TraitDefaultMethodCallChecker
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm.NullabilityInformationSource
@@ -59,7 +60,8 @@ public class KotlinJvmCheckerProvider(private val module: ModuleDescriptor) : Ad
PublicFieldAnnotationChecker()),
additionalCallCheckers = listOf(NeedSyntheticChecker(), JavaAnnotationCallChecker(),
JavaAnnotationMethodCallChecker(), TraitDefaultMethodCallChecker()),
JavaAnnotationMethodCallChecker(), TraitDefaultMethodCallChecker(),
ReflectionAPICallChecker(module)),
additionalTypeCheckers = listOf(JavaNullabilityWarningsChecker()),
additionalSymbolUsageValidators = listOf()
@@ -0,0 +1,70 @@
/*
* 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.resolve.jvm.calls.checkers
import org.jetbrains.kotlin.builtins.KOTLIN_REFLECT_FQ_NAME
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.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
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.types.expressions.OperatorConventions
import kotlin.reflect.jvm.java
/**
* 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 ReflectionAPICallChecker(private val module: ModuleDescriptor) : CallChecker {
private val isReflectionAvailable by lazy {
module.findClassAcrossModuleDependencies(JvmAbi.REFLECTION_FACTORY_IMPL) != null
}
private val kPropertyClasses by lazy {
val reflectionTypes = ReflectionTypes(module)
setOf(reflectionTypes.kProperty0, reflectionTypes.kProperty1, reflectionTypes.kProperty2)
}
override fun <F : CallableDescriptor> check(resolvedCall: ResolvedCall<F>, context: BasicCallResolutionContext) {
if (isReflectionAvailable) return
val descriptor = resolvedCall.getResultingDescriptor()
val containingClass = descriptor.getContainingDeclaration() 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.getName()
when {
name == OperatorConventions.INVOKE -> return
name.asString() == "name" -> return
(name.asString() == "get" || name.asString() == "set") &&
kPropertyClasses.any { kProperty -> DescriptorUtils.isSubclass(containingClass, kProperty) } -> return
}
context.trace.report(NO_REFLECTION_IN_CLASS_PATH.on(resolvedCall.getCall().getCallElement()))
}
}
@@ -59,8 +59,8 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(ErrorsJvm.POSITIONED_VALUE_ARGUMENT_FOR_JAVA_ANNOTATION, "Only named arguments are available for Java annotations");
MAP.put(ErrorsJvm.DEPRECATED_ANNOTATION_METHOD_CALL, "Annotation methods are deprecated. Use property instead");
MAP.put(ErrorsJvm.NO_REFLECTION_IN_CLASS_PATH, "Expression ''{0}'' uses reflection which is not found in compilation classpath. " +
"Make sure you have kotlin-reflect.jar in the classpath", Renderers.ELEMENT_TEXT);
MAP.put(ErrorsJvm.NO_REFLECTION_IN_CLASS_PATH, "Call uses reflection API which is not found in compilation classpath. " +
"Make sure you have kotlin-reflect.jar in the classpath");
MAP.put(ErrorsJvm.NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS,
"Expected type does not accept nulls in {0}, but the value may be null in {1}", Renderers.TO_STRING, Renderers.TO_STRING);
@@ -58,8 +58,7 @@ public interface ErrorsJvm {
DiagnosticFactory0<JetElement> INAPPLICABLE_PUBLIC_FIELD = DiagnosticFactory0.create(ERROR);
// TODO: make this a warning
DiagnosticFactory1<JetExpression, JetExpression> NO_REFLECTION_IN_CLASS_PATH = DiagnosticFactory1.create(ERROR);
DiagnosticFactory0<JetElement> NO_REFLECTION_IN_CLASS_PATH = DiagnosticFactory0.create(WARNING);
enum NullabilityInformationSource {
KOTLIN {