KT-10752: if (inferred) type for an expression refers to a Java class
non-accessible in the current context, it is a compiler error. Otherwise we might generate a CHECKCAST instruction that causes IAE at run-time. Here we are somewhat less permissive then Java (see inaccessibleType.kt in diagnostics tests).
This commit is contained in:
+94
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.SmartList
|
||||||
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
|
||||||
|
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
|
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.psi.KtExpression
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.checkers.AdditionalTypeChecker
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.context.CallResolutionContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.context.ResolutionContext
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
|
|
||||||
|
class JavaTypeAccessibilityChecker : AdditionalTypeChecker {
|
||||||
|
override fun checkType(
|
||||||
|
expression: KtExpression,
|
||||||
|
expressionType: KotlinType,
|
||||||
|
expressionTypeWithSmartCast: KotlinType,
|
||||||
|
c: ResolutionContext<*>
|
||||||
|
) {
|
||||||
|
// NB in Kotlin class hierarchy leading to "pathological" type inference is impossible
|
||||||
|
// due to EXPOSED_SUPER_CLASS & EXPOSED_SUPER_INTERFACE checks.
|
||||||
|
// To avoid superfluous diagnostics in case of invisible member class and so on,
|
||||||
|
// we consider only Java classes as possibly inaccessible.
|
||||||
|
|
||||||
|
val inaccessibleTypes = findInaccessibleJavaTypes(expressionType, c)
|
||||||
|
if (inaccessibleTypes.isNotEmpty()) {
|
||||||
|
c.trace.report(Errors.INACCESSIBLE_TYPE.on(expression, expressionType, inaccessibleTypes))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expressionTypeWithSmartCast != expressionType) {
|
||||||
|
val inaccessibleTypesWithSmartCast = findInaccessibleJavaTypes(expressionTypeWithSmartCast, c)
|
||||||
|
if (inaccessibleTypesWithSmartCast.isNotEmpty()) {
|
||||||
|
c.trace.report(Errors.INACCESSIBLE_TYPE.on(expression, expressionType, inaccessibleTypes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun checkReceiver(
|
||||||
|
receiverParameter: ReceiverParameterDescriptor,
|
||||||
|
receiverArgument: ReceiverValue,
|
||||||
|
safeAccess: Boolean,
|
||||||
|
c: CallResolutionContext<*>
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private fun findInaccessibleJavaTypes(type: KotlinType, c: ResolutionContext<*>): List<KotlinType> {
|
||||||
|
val scopeOwner = c.scope.ownerDescriptor
|
||||||
|
val inaccessibleTypes = SmartList<KotlinType>()
|
||||||
|
findInaccessibleJavaTypesRec(type, scopeOwner, inaccessibleTypes, hashSetOf())
|
||||||
|
return inaccessibleTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findInaccessibleJavaTypesRec(
|
||||||
|
type: KotlinType,
|
||||||
|
scopeOwner: DeclarationDescriptor,
|
||||||
|
inaccessibleTypes: SmartList<KotlinType>,
|
||||||
|
visitedTypeConstructors: MutableSet<DeclarationDescriptor>
|
||||||
|
) {
|
||||||
|
val typeConstructor = type.constructor.declarationDescriptor
|
||||||
|
if (typeConstructor is ClassDescriptor) {
|
||||||
|
if (visitedTypeConstructors.contains(typeConstructor)) return
|
||||||
|
visitedTypeConstructors.add(typeConstructor)
|
||||||
|
|
||||||
|
if (typeConstructor is JavaClassDescriptor && !Visibilities.isVisibleWithIrrelevantReceiver(typeConstructor, scopeOwner)) {
|
||||||
|
inaccessibleTypes.add(type)
|
||||||
|
}
|
||||||
|
for (typeProjection in type.arguments) {
|
||||||
|
findInaccessibleJavaTypesRec(typeProjection.type, scopeOwner, inaccessibleTypes, visitedTypeConstructors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+2
-1
@@ -57,7 +57,8 @@ object JvmPlatformConfigurator : PlatformConfigurator(
|
|||||||
additionalTypeCheckers = listOf(
|
additionalTypeCheckers = listOf(
|
||||||
JavaNullabilityWarningsChecker(),
|
JavaNullabilityWarningsChecker(),
|
||||||
RuntimeAssertionsTypeChecker,
|
RuntimeAssertionsTypeChecker,
|
||||||
JavaGenericVarianceViolationTypeChecker
|
JavaGenericVarianceViolationTypeChecker,
|
||||||
|
JavaTypeAccessibilityChecker()
|
||||||
),
|
),
|
||||||
|
|
||||||
additionalSymbolUsageValidators = listOf(),
|
additionalSymbolUsageValidators = listOf(),
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ public interface Errors {
|
|||||||
DiagnosticFactory2<KtSuperTypeListEntry, EffectiveVisibility, EffectiveVisibility> EXPOSED_SUPER_CLASS = DiagnosticFactory2.create(ERROR);
|
DiagnosticFactory2<KtSuperTypeListEntry, EffectiveVisibility, EffectiveVisibility> EXPOSED_SUPER_CLASS = DiagnosticFactory2.create(ERROR);
|
||||||
DiagnosticFactory2<KtSuperTypeListEntry, EffectiveVisibility, EffectiveVisibility> EXPOSED_SUPER_INTERFACE = DiagnosticFactory2.create(ERROR);
|
DiagnosticFactory2<KtSuperTypeListEntry, EffectiveVisibility, EffectiveVisibility> EXPOSED_SUPER_INTERFACE = DiagnosticFactory2.create(ERROR);
|
||||||
|
|
||||||
|
DiagnosticFactory2<KtExpression, KotlinType, Collection<KotlinType>> INACCESSIBLE_TYPE = DiagnosticFactory2.create(ERROR);
|
||||||
|
|
||||||
DiagnosticFactory1<KtElement, Collection<ClassDescriptor>> PLATFORM_CLASS_MAPPED_TO_KOTLIN = DiagnosticFactory1.create(WARNING);
|
DiagnosticFactory1<KtElement, Collection<ClassDescriptor>> PLATFORM_CLASS_MAPPED_TO_KOTLIN = DiagnosticFactory1.create(WARNING);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
+2
@@ -122,6 +122,8 @@ public class DefaultErrorMessages {
|
|||||||
MAP.put(EXPOSED_SUPER_CLASS, "Subclass effective visibility ''{0}'' should be the same or less permissive than its superclass effective visibility ''{1}''", TO_STRING, TO_STRING);
|
MAP.put(EXPOSED_SUPER_CLASS, "Subclass effective visibility ''{0}'' should be the same or less permissive than its superclass effective visibility ''{1}''", TO_STRING, TO_STRING);
|
||||||
MAP.put(EXPOSED_SUPER_INTERFACE, "Sub-interface effective visibility ''{0}'' should be the same or less permissive than its super-interface effective visibility ''{1}''", TO_STRING, TO_STRING);
|
MAP.put(EXPOSED_SUPER_INTERFACE, "Sub-interface effective visibility ''{0}'' should be the same or less permissive than its super-interface effective visibility ''{1}''", TO_STRING, TO_STRING);
|
||||||
|
|
||||||
|
MAP.put(INACCESSIBLE_TYPE, "Type {0} is inaccessible in this context due to: {1}", RENDER_TYPE, RENDER_COLLECTION_OF_TYPES);
|
||||||
|
|
||||||
MAP.put(REDECLARATION, "Redeclaration: {0}", STRING);
|
MAP.put(REDECLARATION, "Redeclaration: {0}", STRING);
|
||||||
MAP.put(NAME_SHADOWING, "Name shadowed: {0}", STRING);
|
MAP.put(NAME_SHADOWING, "Name shadowed: {0}", STRING);
|
||||||
MAP.put(ACCESSOR_PARAMETER_NAME_SHADOWING, "Accessor parameter name 'field' is shadowed by backing field variable");
|
MAP.put(ACCESSOR_PARAMETER_NAME_SHADOWING, "Accessor parameter name 'field' is shadowed by backing field variable");
|
||||||
|
|||||||
+4
-2
@@ -181,8 +181,10 @@ public class ControlStructureTypingVisitor extends ExpressionTypingVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If break or continue was possible, take condition check info as the jump info
|
// If break or continue was possible, take condition check info as the jump info
|
||||||
return TypeInfoFactoryKt.createTypeInfo(resultType, resultDataFlowInfo, loopBreakContinuePossible,
|
return TypeInfoFactoryKt.createTypeInfo(
|
||||||
loopBreakContinuePossibleInCondition ? context.dataFlowInfo : conditionDataFlowInfo);
|
components.dataFlowAnalyzer.checkType(resultType, ifExpression, contextWithExpectedType),
|
||||||
|
resultDataFlowInfo, loopBreakContinuePossible,
|
||||||
|
loopBreakContinuePossibleInCondition ? context.dataFlowInfo : conditionDataFlowInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
// !DIAGNOSTICS: -USELESS_CAST -UNUSED_PARAMETER -UNUSED_VARIABLE
|
||||||
|
|
||||||
|
// FILE: j/Base.java
|
||||||
|
package j;
|
||||||
|
public interface Base {
|
||||||
|
void foo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: j/Impl.java
|
||||||
|
package j;
|
||||||
|
|
||||||
|
/* package */ abstract class Impl implements Base {
|
||||||
|
public void foo() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FILE: j/Derived1.java
|
||||||
|
package j;
|
||||||
|
|
||||||
|
public class Derived1 extends Impl {}
|
||||||
|
|
||||||
|
// FILE: j/Derived2.java
|
||||||
|
package j;
|
||||||
|
|
||||||
|
public class Derived2 extends Impl {}
|
||||||
|
|
||||||
|
// FILE: k/Client.kt
|
||||||
|
package k
|
||||||
|
|
||||||
|
import j.*
|
||||||
|
|
||||||
|
val d1 = Derived1()
|
||||||
|
val d2 = Derived2()
|
||||||
|
|
||||||
|
fun <T> select(x1: T, x2: T) = x1
|
||||||
|
fun <T> selectn(vararg xx: T) = xx[0]
|
||||||
|
fun <T : Base> foo(x: T) = x.foo()
|
||||||
|
fun <T> listOf2(x1: T, x2: T): List<T> = null!!
|
||||||
|
fun <T> arrayOf2(x1: T, x2: T): Array<T> = null!!
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val test1: Base = if (true) d1 else d2
|
||||||
|
|
||||||
|
val test2 = <!INACCESSIBLE_TYPE!>if (true) d1 else d2<!>
|
||||||
|
|
||||||
|
val test3 = <!INACCESSIBLE_TYPE!>when {
|
||||||
|
true -> d1
|
||||||
|
else -> d2
|
||||||
|
}<!>
|
||||||
|
|
||||||
|
val test4: Base = when {
|
||||||
|
true -> d1
|
||||||
|
else -> d2
|
||||||
|
}
|
||||||
|
|
||||||
|
val test5 = <!INACCESSIBLE_TYPE!>select(d1, d2)<!>
|
||||||
|
|
||||||
|
val test6 = select<Base>(d1, d2)
|
||||||
|
|
||||||
|
val test7 = select(d1 as Base, d2)
|
||||||
|
|
||||||
|
val test8 = <!INACCESSIBLE_TYPE!>selectn(d1, d2)<!>
|
||||||
|
|
||||||
|
val test9 = selectn<Base>(d1, d2)
|
||||||
|
|
||||||
|
val test10 = <!INACCESSIBLE_TYPE!>listOf2(d1, d2)<!>
|
||||||
|
|
||||||
|
val test11: List<Base> = <!INACCESSIBLE_TYPE!>listOf2(d1, d2)<!>
|
||||||
|
// NB Inferred type is List<Impl> because List is covariant.
|
||||||
|
|
||||||
|
val test12 = listOf2<Base>(d1, d2)
|
||||||
|
|
||||||
|
val test13 = <!INACCESSIBLE_TYPE!>arrayOf2(d1, d2)<!>
|
||||||
|
|
||||||
|
val test14: Array<Base> = arrayOf2(d1, d2)
|
||||||
|
// NB Inferred type is Array<Base> because Array is invariant.
|
||||||
|
|
||||||
|
val test15 = arrayOf2<Base>(d1, d2)
|
||||||
|
|
||||||
|
for (test16 in <!INACCESSIBLE_TYPE!>listOf2(d1, d2)<!>) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testOkInJava() {
|
||||||
|
// The following is Ok in Java, but is an error in Kotlin.
|
||||||
|
// TODO do not generate unneeded CHECKCASTs.
|
||||||
|
// TODO do not report INACCESSIBLE_TYPE for corresponding cases.
|
||||||
|
<!INACCESSIBLE_TYPE!>select(d1, d2)<!>
|
||||||
|
foo(<!INACCESSIBLE_TYPE!>select(d1, d2)<!>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package
|
||||||
|
|
||||||
|
package k {
|
||||||
|
public val d1: j.Derived1
|
||||||
|
public val d2: j.Derived2
|
||||||
|
public fun </*0*/ T> arrayOf2(/*0*/ x1: T, /*1*/ x2: T): kotlin.Array<T>
|
||||||
|
public fun </*0*/ T : j.Base> foo(/*0*/ x: T): kotlin.Unit
|
||||||
|
public fun </*0*/ T> listOf2(/*0*/ x1: T, /*1*/ x2: T): kotlin.collections.List<T>
|
||||||
|
public fun </*0*/ T> select(/*0*/ x1: T, /*1*/ x2: T): T
|
||||||
|
public fun </*0*/ T> selectn(/*0*/ vararg xx: T /*kotlin.Array<out T>*/): T
|
||||||
|
public fun test(): kotlin.Unit
|
||||||
|
public fun testOkInJava(): kotlin.Unit
|
||||||
|
}
|
||||||
@@ -30,6 +30,5 @@ package other
|
|||||||
import test.My
|
import test.My
|
||||||
|
|
||||||
class Your {
|
class Your {
|
||||||
// Ok but dangerous: internal vs package-private in different package
|
internal fun bar() = <!INACCESSIBLE_TYPE!>My.foo()<!>
|
||||||
internal fun bar() = My.foo()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,19 +26,19 @@ package b
|
|||||||
|
|
||||||
import a.<!INVISIBLE_REFERENCE!>MyJavaClass<!>
|
import a.<!INVISIBLE_REFERENCE!>MyJavaClass<!>
|
||||||
|
|
||||||
val <!EXPOSED_PROPERTY_TYPE!>mc1<!> = <!INVISIBLE_MEMBER!>MyJavaClass<!>()
|
val <!EXPOSED_PROPERTY_TYPE!>mc1<!> = <!INACCESSIBLE_TYPE!><!INVISIBLE_MEMBER!>MyJavaClass<!>()<!>
|
||||||
|
|
||||||
val x = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_MEMBER!>staticMethod<!>()
|
val x = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_MEMBER!>staticMethod<!>()
|
||||||
val y = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_REFERENCE!>NestedClass<!>.<!INVISIBLE_MEMBER!>staticMethodOfNested<!>()
|
val y = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_REFERENCE!>NestedClass<!>.<!INVISIBLE_MEMBER!>staticMethodOfNested<!>()
|
||||||
val <!EXPOSED_PROPERTY_TYPE!>z<!> = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_MEMBER!>NestedClass<!>()
|
val <!EXPOSED_PROPERTY_TYPE!>z<!> = <!INACCESSIBLE_TYPE!><!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_MEMBER!>NestedClass<!>()<!>
|
||||||
|
|
||||||
//FILE: c.kt
|
//FILE: c.kt
|
||||||
package a.c
|
package a.c
|
||||||
|
|
||||||
import a.<!INVISIBLE_REFERENCE!>MyJavaClass<!>
|
import a.<!INVISIBLE_REFERENCE!>MyJavaClass<!>
|
||||||
|
|
||||||
val <!EXPOSED_PROPERTY_TYPE!>mc1<!> = <!INVISIBLE_MEMBER!>MyJavaClass<!>()
|
val <!EXPOSED_PROPERTY_TYPE!>mc1<!> = <!INACCESSIBLE_TYPE!><!INVISIBLE_MEMBER!>MyJavaClass<!>()<!>
|
||||||
|
|
||||||
val x = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_MEMBER!>staticMethod<!>()
|
val x = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_MEMBER!>staticMethod<!>()
|
||||||
val y = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_REFERENCE!>NestedClass<!>.<!INVISIBLE_MEMBER!>staticMethodOfNested<!>()
|
val y = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_REFERENCE!>NestedClass<!>.<!INVISIBLE_MEMBER!>staticMethodOfNested<!>()
|
||||||
val <!EXPOSED_PROPERTY_TYPE!>z<!> = <!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_MEMBER!>NestedClass<!>()
|
val <!EXPOSED_PROPERTY_TYPE!>z<!> = <!INACCESSIBLE_TYPE!><!INVISIBLE_REFERENCE!>MyJavaClass<!>.<!INVISIBLE_MEMBER!>NestedClass<!>()<!>
|
||||||
@@ -6180,6 +6180,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("inaccessibleType.kt")
|
||||||
|
public void testInaccessibleType() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/exposed/inaccessibleType.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("internal.kt")
|
@TestMetadata("internal.kt")
|
||||||
public void testInternal() throws Exception {
|
public void testInternal() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/exposed/internal.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/exposed/internal.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user