FIR visibility check: support private class members

This commit is contained in:
Mikhail Glukhikh
2019-11-06 15:19:42 +03:00
parent a5ad05083a
commit e96aeb77a6
12 changed files with 310 additions and 78 deletions
@@ -85,6 +85,7 @@ class FirCallResolver(
typeArguments, typeArguments,
session, session,
file, file,
transformer.components.implicitReceiverStack,
transformer.components.container transformer.components.container
) { it.resultType } ) { it.resultType }
towerResolver.reset() towerResolver.reset()
@@ -143,6 +144,7 @@ class FirCallResolver(
emptyList(), emptyList(),
session, session,
file, file,
transformer.components.implicitReceiverStack,
transformer.components.container transformer.components.container
) { it.resultType } ) { it.resultType }
towerResolver.reset() towerResolver.reset()
@@ -322,6 +324,7 @@ class FirCallResolver(
emptyList(), emptyList(),
session, session,
file, file,
transformer.components.implicitReceiverStack,
transformer.components.container, transformer.components.container,
expectedType, expectedType,
outerConstraintSystemBuilder, outerConstraintSystemBuilder,
@@ -8,11 +8,13 @@ package org.jetbrains.kotlin.fir.resolve.calls
import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.declarations.FirDeclaration import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirFile import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirValueParameter import org.jetbrains.kotlin.fir.declarations.FirValueParameter
import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.FirExpression
import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression import org.jetbrains.kotlin.fir.expressions.impl.FirNoReceiverExpression
import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents
import org.jetbrains.kotlin.fir.resolve.DoubleColonLHS import org.jetbrains.kotlin.fir.resolve.DoubleColonLHS
import org.jetbrains.kotlin.fir.resolve.ImplicitReceiverStack
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.ConeKotlinType
@@ -34,7 +36,8 @@ class CallInfo(
val typeArguments: List<FirTypeProjection>, val typeArguments: List<FirTypeProjection>,
val session: FirSession, val session: FirSession,
val containingFile: FirFile, val containingFile: FirFile,
val container: FirDeclaration, val implicitReceiverStack: ImplicitReceiverStack,
val containingDeclaration: FirDeclaration,
// Three properties for callable references only // Three properties for callable references only
val expectedType: ConeKotlinType? = null, val expectedType: ConeKotlinType? = null,
@@ -98,7 +98,8 @@ class InvokeReceiverCandidateCollector(
invokeCallInfo.typeArguments, invokeCallInfo.typeArguments,
session, session,
invokeCallInfo.containingFile, invokeCallInfo.containingFile,
invokeCallInfo.container, invokeCallInfo.implicitReceiverStack,
invokeCallInfo.containingDeclaration,
invokeCallInfo.expectedType, invokeCallInfo.expectedType,
invokeCallInfo.outerCSBuilder, invokeCallInfo.outerCSBuilder,
invokeCallInfo.lhs, invokeCallInfo.lhs,
@@ -71,7 +71,8 @@ fun createFunctionConsumer(
callInfo.typeArguments, callInfo.typeArguments,
bodyResolveComponents.session, bodyResolveComponents.session,
callInfo.containingFile, callInfo.containingFile,
callInfo.container, callInfo.implicitReceiverStack,
callInfo.containingDeclaration,
callInfo.expectedType, callInfo.expectedType,
callInfo.outerCSBuilder, callInfo.outerCSBuilder,
callInfo.lhs, callInfo.lhs,
@@ -9,19 +9,18 @@ import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier
import org.jetbrains.kotlin.fir.render import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.DoubleColonLHS import org.jetbrains.kotlin.fir.resolve.*
import org.jetbrains.kotlin.fir.resolve.createFunctionalType
import org.jetbrains.kotlin.fir.resolve.createKPropertyType
import org.jetbrains.kotlin.fir.resolve.firProvider
import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType import org.jetbrains.kotlin.fir.types.ConeKotlinErrorType
import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef import org.jetbrains.kotlin.fir.types.FirResolvedTypeRef
import org.jetbrains.kotlin.fir.types.coneTypeSafe import org.jetbrains.kotlin.fir.types.coneTypeSafe
import org.jetbrains.kotlin.load.java.JavaVisibilities import org.jetbrains.kotlin.load.java.JavaVisibilities
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemOperation import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemOperation
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
@@ -275,81 +274,115 @@ internal object CheckVisibility : CheckerStage() {
} }
} }
private fun ImplicitReceiverStack.canSeePrivateMemberOf(ownerId: ClassId): Boolean {
for (implicitReceiverValue in receiversAsReversed()) {
if (implicitReceiverValue !is ImplicitDispatchReceiverValue) continue
if (implicitReceiverValue.boundSymbol.classId == ownerId) return true
}
return false
}
private fun ClassId.asLocal(): ClassId = ClassId(packageFqName, relativeClassName, true)
private suspend fun checkVisibility(
declaration: FirMemberDeclaration,
symbol: AbstractFirBasedSymbol<*>,
sink: CheckerSink,
callInfo: CallInfo
): Boolean {
val useSiteFile = callInfo.containingFile
val implicitReceiverStack = callInfo.implicitReceiverStack
val visible = when (declaration.visibility) {
JavaVisibilities.PACKAGE_VISIBILITY -> {
symbol.packageFqName() == useSiteFile.packageFqName
}
Visibilities.PRIVATE, Visibilities.PRIVATE_TO_THIS -> {
if (declaration.session == callInfo.session) {
val provider = callInfo.session.firProvider
when (symbol) {
is FirClassLikeSymbol<*> -> {
val classId = symbol.classId
val ownerId = classId.outerClassId
when {
classId.isLocal -> {
// Normally should not be here
implicitReceiverStack.canSeePrivateMemberOf(ownerId?.asLocal() ?: classId)
}
ownerId == null -> {
// Top-level: visible in file
provider.getFirClassifierContainerFile(classId) == useSiteFile
}
else -> {
// Member: visible inside parent class, including all its member classes
implicitReceiverStack.canSeePrivateMemberOf(ownerId)
}
}
}
is FirCallableSymbol<*> -> {
val candidateFile = provider.getFirCallableContainerFile(symbol)
val ownerId = symbol.callableId.classId
when {
candidateFile == null -> {
// Local
ownerId != null && implicitReceiverStack.canSeePrivateMemberOf(ownerId.asLocal())
}
ownerId == null -> {
// Top-level: visible in file
candidateFile == useSiteFile
}
else -> {
// Member: visible inside parent class, including all its member classes
implicitReceiverStack.canSeePrivateMemberOf(ownerId)
}
}
}
else -> {
throw AssertionError("Unsupported visibility check for ${declaration.javaClass}: ${declaration.render()}")
}
}
} else {
false
}
}
Visibilities.INTERNAL -> {
declaration.session == callInfo.session
}
Visibilities.PROTECTED -> {
true // TODO: Support protected visibility
}
JavaVisibilities.PROTECTED_AND_PACKAGE -> {
if (symbol.packageFqName() == useSiteFile.packageFqName) {
true
} else {
true // TODO: Support protected visibility
}
}
else -> true
}
if (!visible) {
sink.yieldApplicability(CandidateApplicability.HIDDEN)
return false
}
return true
}
override suspend fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) { override suspend fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) {
val symbol = candidate.symbol val symbol = candidate.symbol
val declaration = symbol.fir val declaration = symbol.fir
if (declaration is FirMemberDeclaration) { if (declaration is FirMemberDeclaration) {
val useSiteFile = callInfo.containingFile if (!checkVisibility(declaration, symbol, sink, callInfo)) {
val visible = when (declaration.visibility) { return
JavaVisibilities.PACKAGE_VISIBILITY -> {
symbol.packageFqName() == useSiteFile.packageFqName
}
Visibilities.PRIVATE, Visibilities.PRIVATE_TO_THIS -> {
if (declaration.session == callInfo.session) {
val provider = callInfo.session.firProvider
when (symbol) {
is FirClassLikeSymbol<*> -> {
val classId = symbol.classId
when {
classId.isLocal -> {
// Normally should not be here
TODO()
}
!classId.isNestedClass -> {
// Top-level: visible in file
provider.getFirClassifierContainerFile(classId) == useSiteFile
}
else -> {
// Member: visible inside parent class, including all its member classes
TODO()
}
}
}
is FirCallableSymbol<*> -> {
val candidateFile = provider.getFirCallableContainerFile(symbol)
when {
candidateFile == null -> {
// Local
TODO()
}
symbol.callableId.classId == null -> {
// Top-level: visible in file
candidateFile == useSiteFile
}
else -> {
// Member: visible inside parent class, including all its member classes
TODO()
}
}
}
else -> {
throw AssertionError("Unsupported visibility check for ${declaration.javaClass}: ${declaration.render()}")
}
}
} else {
false
}
}
Visibilities.INTERNAL -> {
declaration.session == callInfo.session
}
Visibilities.PROTECTED -> {
// Support protected visibility...
TODO()
}
JavaVisibilities.PROTECTED_AND_PACKAGE -> {
if (symbol.packageFqName() == useSiteFile.packageFqName) {
true
} else {
// Support protected visibility...
TODO()
}
}
else -> true
} }
}
if (!visible) { if (declaration is FirConstructor) {
sink.yieldApplicability(CandidateApplicability.HIDDEN) val ownerClassId = declaration.symbol.callableId.classId!!
val provider = declaration.session.firSymbolProvider
val classSymbol = provider.getClassLikeSymbolByFqName(ownerClassId)
if (classSymbol is FirRegularClassSymbol) {
checkVisibility(classSymbol.fir, classSymbol, sink, callInfo)
} }
} }
} }
@@ -131,7 +131,8 @@ class FirSyntheticCallGenerator(
typeArguments = emptyList(), typeArguments = emptyList(),
session = session, session = session,
containingFile = file, containingFile = file,
container = container implicitReceiverStack = implicitReceiverStack,
containingDeclaration = container
) { it.resultType } ) { it.resultType }
private fun generateSyntheticSelectFunction(callableId: CallableId, isVararg: Boolean = true): FirSimpleFunctionImpl { private fun generateSyntheticSelectFunction(callableId: CallableId, isVararg: Boolean = true): FirSimpleFunctionImpl {
@@ -0,0 +1,23 @@
// FILE: Node.java
public interface Node<R> {
R result();
}
// FILE: foo.kt
fun foo(): Boolean {
object : Node<Boolean> {
private var result = false
fun bar(): Boolean {
return !result
}
// Cannot see private member of local class / anonymous object
override fun result() = result
}
val some = true
return !some
}
@@ -0,0 +1,24 @@
FILE: foo.kt
public final fun foo(): R|kotlin/Boolean| {
object : R|Node<kotlin/Boolean>| {
private constructor(): R|kotlin/Any| {
super<R|kotlin/Any|>()
}
private final var result: R|kotlin/Boolean| = Boolean(false)
private get(): R|kotlin/Boolean|
private set(value: R|kotlin/Boolean|): R|kotlin/Unit|
public final fun bar(): R|kotlin/Boolean| {
^bar R|/anonymous.result|.R|kotlin/Boolean.not|()
}
public final override fun result(): R|kotlin/Boolean| {
^result R|/anonymous.result|
}
}
lval some: R|kotlin/Boolean| = Boolean(true)
^foo R|<local>/some|.R|kotlin/Boolean.not|()
}
@@ -0,0 +1,57 @@
// FILE: first.kt
private fun foo() {}
private class Private {
private fun bar() {}
fun baz() {
bar()
Nested()
}
inner class Inner {
fun foo() {
bar()
}
}
private class Nested
}
fun withLocals() {
class Local {
private fun bar()
fun baz() {
bar()
Inner()
}
private inner class Inner {
fun foo() {
bar()
}
}
}
Local().baz()
Local().<!INAPPLICABLE_CANDIDATE!>bar<!>() // hidden
}
fun test() {
foo()
Private().baz()
Private().Inner()
Private().<!INAPPLICABLE_CANDIDATE!>bar<!>() // hidden
Private.<!INAPPLICABLE_CANDIDATE!>Nested<!>() // hidden
}
// FILE: second.kt
fun secondTest() {
<!INAPPLICABLE_CANDIDATE!>foo<!>() // hidden
<!INAPPLICABLE_CANDIDATE!>Private<!>() // hidden
}
@@ -0,0 +1,76 @@
FILE: first.kt
private final fun foo(): R|kotlin/Unit| {
}
private final class Private : R|kotlin/Any| {
public constructor(): R|Private| {
super<R|kotlin/Any|>()
}
private final fun bar(): R|kotlin/Unit| {
}
public final fun baz(): R|kotlin/Unit| {
this@R|/Private|.R|/Private.bar|()
R|/Private.Nested.Nested|()
}
public final inner class Inner : R|kotlin/Any| {
public constructor(): R|Private.Inner| {
super<R|kotlin/Any|>()
}
public final fun foo(): R|kotlin/Unit| {
this@R|/Private|.R|/Private.bar|()
}
}
private final class Nested : R|kotlin/Any| {
public constructor(): R|Private.Nested| {
super<R|kotlin/Any|>()
}
}
}
public final fun withLocals(): R|kotlin/Unit| {
local final class Local : R|kotlin/Any| {
public constructor(): R|Local| {
super<R|kotlin/Any|>()
}
private final fun bar(): R|kotlin/Unit|
public final fun baz(): R|kotlin/Unit| {
R|/Local.bar|()
R|/Local.Inner.Inner|()
}
local final inner class Inner : R|kotlin/Any| {
public constructor(): R|Local.Inner| {
super<R|kotlin/Any|>()
}
public final fun foo(): R|kotlin/Unit| {
R|/Local.bar|()
}
}
}
R|/Local.Local|().R|/Local.baz|()
R|/Local.Local|().<Inapplicable(HIDDEN): [/Local.bar]>#()
}
public final fun test(): R|kotlin/Unit| {
R|/foo|()
R|/Private.Private|().R|/Private.baz|()
R|/Private.Private|().R|/Private.Inner.Inner|()
R|/Private.Private|().<Inapplicable(HIDDEN): [/Private.bar]>#()
Q|Private|.<Inapplicable(HIDDEN): [/Private.Nested.Nested]>#()
}
FILE: second.kt
public final fun secondTest(): R|kotlin/Unit| {
<Inapplicable(HIDDEN): [/foo]>#()
<Inapplicable(HIDDEN): [/Private.Private]>#()
}
@@ -414,6 +414,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
runTest("compiler/fir/resolve/testData/resolve/expresssions/localTypes.kt"); runTest("compiler/fir/resolve/testData/resolve/expresssions/localTypes.kt");
} }
@TestMetadata("localWithBooleanNot.kt")
public void testLocalWithBooleanNot() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/expresssions/localWithBooleanNot.kt");
}
@TestMetadata("memberExtension.kt") @TestMetadata("memberExtension.kt")
public void testMemberExtension() throws Exception { public void testMemberExtension() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/expresssions/memberExtension.kt"); runTest("compiler/fir/resolve/testData/resolve/expresssions/memberExtension.kt");
@@ -439,6 +444,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
runTest("compiler/fir/resolve/testData/resolve/expresssions/privateObjectLiteral.kt"); runTest("compiler/fir/resolve/testData/resolve/expresssions/privateObjectLiteral.kt");
} }
@TestMetadata("privateVisibility.kt")
public void testPrivateVisibility() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/expresssions/privateVisibility.kt");
}
@TestMetadata("qualifiedExpressions.kt") @TestMetadata("qualifiedExpressions.kt")
public void testQualifiedExpressions() throws Exception { public void testQualifiedExpressions() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/expresssions/qualifiedExpressions.kt"); runTest("compiler/fir/resolve/testData/resolve/expresssions/qualifiedExpressions.kt");
@@ -100,7 +100,7 @@ FILE fqName:<root> fileName:/objectAsCallable.kt
PROPERTY name:test1 visibility:public modality:FINAL [val] PROPERTY name:test1 visibility:public modality:FINAL [val]
FIELD PROPERTY_BACKING_FIELD name:test1 type:IrErrorType visibility:private [final,static] FIELD PROPERTY_BACKING_FIELD name:test1 type:IrErrorType visibility:private [final,static]
EXPRESSION_BODY EXPRESSION_BODY
ERROR_CALL 'Unresolved reference: <Inapplicable(PARAMETER_MAPPING_ERROR): [/A.A]>#' type=IrErrorType ERROR_CALL 'Unresolved reference: <Inapplicable(HIDDEN): [/A.A]>#' type=IrErrorType
CONST Int type=kotlin.Int value=42 CONST Int type=kotlin.Int value=42
FUN DEFAULT_PROPERTY_ACCESSOR name:<get-test1> visibility:public modality:FINAL <> () returnType:IrErrorType FUN DEFAULT_PROPERTY_ACCESSOR name:<get-test1> visibility:public modality:FINAL <> () returnType:IrErrorType
correspondingProperty: PROPERTY name:test1 visibility:public modality:FINAL [val] correspondingProperty: PROPERTY name:test1 visibility:public modality:FINAL [val]