diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaModuleBasedSession.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaModuleBasedSession.kt index 8be814a4255..6eecba54ea0 100644 --- a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaModuleBasedSession.kt +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaModuleBasedSession.kt @@ -39,6 +39,7 @@ class FirJavaModuleBasedSession private constructor( registerResolveComponents() registerCheckersComponent() registerJvmCallConflictResolverFactory() + registerJavaVisibilityChecker() val kotlinScopeProvider = KotlinScopeProvider(::wrapScopeWithJvmMapped) diff --git a/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaVisibilityChecker.kt b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaVisibilityChecker.kt new file mode 100644 index 00000000000..ede0388c179 --- /dev/null +++ b/compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaVisibilityChecker.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.java + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.FirVisibilityChecker +import org.jetbrains.kotlin.fir.JavaVisibilities +import org.jetbrains.kotlin.fir.Visibility +import org.jetbrains.kotlin.fir.declarations.FirDeclaration +import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.resolve.calls.Candidate +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.name.ClassId + +object FirJavaVisibilityChecker : FirVisibilityChecker() { + override fun platformVisibilityCheck( + declarationVisibility: Visibility, + symbol: AbstractFirBasedSymbol<*>, + useSiteFile: FirFile, + ownerId: ClassId?, + containingDeclarations: List, + candidate: Candidate, + session: FirSession + ): Boolean { + return when (declarationVisibility) { + JavaVisibilities.ProtectedAndPackage, JavaVisibilities.ProtectedStaticVisibility -> { + if (symbol.packageFqName() == useSiteFile.packageFqName) { + true + } else { + ownerId != null && canSeeProtectedMemberOf(containingDeclarations, candidate.dispatchReceiverValue, ownerId, session) + } + } + + JavaVisibilities.PackageVisibility -> { + symbol.packageFqName() == useSiteFile.packageFqName + } + + else -> true + } + } +} + +fun FirSession.registerJavaVisibilityChecker() { + register(FirVisibilityChecker::class, FirJavaVisibilityChecker) +} diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt new file mode 100644 index 00000000000..f31ddf255a6 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir + +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.resolve.calls.* +import org.jetbrains.kotlin.fir.resolve.firProvider +import org.jetbrains.kotlin.fir.resolve.firSymbolProvider +import org.jetbrains.kotlin.fir.resolve.fullyExpandedType +import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.fir.types.ConeClassLikeType +import org.jetbrains.kotlin.load.java.JavaVisibilities +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName + +abstract class FirVisibilityChecker : FirSessionComponent { + object Default : FirVisibilityChecker() { + override fun platformVisibilityCheck( + declarationVisibility: Visibility, + symbol: AbstractFirBasedSymbol<*>, + useSiteFile: FirFile, + ownerId: ClassId?, + containingDeclarations: List, + candidate: Candidate, + session: FirSession + ): Boolean { + return true + } + } + + fun isVisible( + declaration: FirMemberDeclaration, + symbol: AbstractFirBasedSymbol<*>, + candidate: Candidate + ): Boolean { + val callInfo = candidate.callInfo + val useSiteFile = callInfo.containingFile + val containingDeclarations = callInfo.containingDeclarations + val session = callInfo.session + val provider = session.firProvider + val ownerId = symbol.getOwnerId() + + return when (declaration.visibility) { + Visibilities.Internal -> { + declaration.session == callInfo.session + } + Visibilities.Private, Visibilities.PrivateToThis -> { + if (declaration.session == callInfo.session) { + if (ownerId == null || declaration is FirConstructor && declaration.isFromSealedClass) { + val candidateFile = when (symbol) { + is FirClassLikeSymbol<*> -> provider.getFirClassifierContainerFileIfAny(symbol) + is FirCallableSymbol<*> -> provider.getFirCallableContainerFile(symbol) + else -> null + } + // Top-level: visible in file + candidateFile == useSiteFile + } else { + // Member: visible inside parent class, including all its member classes + canSeePrivateMemberOf(containingDeclarations, ownerId, session) + } + } else { + declaration is FirSimpleFunction && declaration.isAllowedToBeAccessedFromOutside() + } + } + + Visibilities.Protected -> { + ownerId != null && canSeeProtectedMemberOf(containingDeclarations, candidate.dispatchReceiverValue, ownerId, session) + } + + else -> platformVisibilityCheck( + declaration.visibility, + symbol, + useSiteFile, + ownerId, + containingDeclarations, + candidate, + session + ) + } + } + + protected abstract fun platformVisibilityCheck( + declarationVisibility: Visibility, + symbol: AbstractFirBasedSymbol<*>, + useSiteFile: FirFile, + ownerId: ClassId?, + containingDeclarations: List, + candidate: Candidate, + session: FirSession + ): Boolean + + private fun canSeePrivateMemberOf( + containingDeclarationOfUseSite: List, + ownerId: ClassId, + session: FirSession + ): Boolean { + ownerId.ownerIfCompanion(session)?.let { companionOwnerClassId -> + return canSeePrivateMemberOf(containingDeclarationOfUseSite, companionOwnerClassId, session) + } + + for (declaration in containingDeclarationOfUseSite) { + if (declaration !is FirClass<*>) continue + val boundSymbol = declaration.symbol + if (boundSymbol.classId.isSame(ownerId)) { + return true + } + } + + return false + } + + // 'local' isn't taken into account here + private fun ClassId.isSame(other: ClassId): Boolean = + packageFqName == other.packageFqName && relativeClassName == other.relativeClassName + + private fun ClassId.ownerIfCompanion(session: FirSession): ClassId? { + if (outerClassId == null || isLocal) return null + val ownerSymbol = session.firSymbolProvider.getClassLikeSymbolByFqName(this) as? FirRegularClassSymbol + + return outerClassId.takeIf { ownerSymbol?.fir?.isCompanion == true } + } + + private fun canSeeProtectedMemberOf( + containingUseSiteClass: FirClass<*>, + dispatchReceiver: ReceiverValue?, + ownerId: ClassId, session: FirSession + ): Boolean { + dispatchReceiver?.ownerIfCompanion(session)?.let { companionOwnerClassId -> + if (containingUseSiteClass.isSubClass(companionOwnerClassId, session)) return true + } + + // TODO: Add check for receiver, see org.jetbrains.kotlin.descriptors.Visibility#doesReceiverFitForProtectedVisibility + return containingUseSiteClass.isSubClass(ownerId, session) + } + + private fun FirClass<*>.isSubClass(ownerId: ClassId, session: FirSession): Boolean { + if (classId.isSame(ownerId)) return true + + return lookupSuperTypes(this, lookupInterfaces = true, deep = true, session).any { superType -> + (superType as? ConeClassLikeType)?.fullyExpandedType(session)?.lookupTag?.classId?.isSame(ownerId) == true + } + } + + private fun ReceiverValue?.ownerIfCompanion(session: FirSession): ClassId? = + (this?.type as? ConeClassLikeType)?.lookupTag?.classId?.ownerIfCompanion(session) + + // monitorEnter/monitorExit are the only functions which are accessed "illegally" (see kotlin/util/Synchronized.kt). + // Since they are intrinsified in the codegen, FIR should treat it as visible. + private fun FirSimpleFunction.isAllowedToBeAccessedFromOutside(): Boolean { + if (!isFromLibrary) return false + val packageName = symbol.callableId.packageName.asString() + val name = name.asString() + return packageName == "kotlin.jvm.internal.unsafe" && + (name == "monitorEnter" || name == "monitorExit") + } + + private fun AbstractFirBasedSymbol<*>.getOwnerId(): ClassId? { + return when (this) { + is FirClassLikeSymbol<*> -> { + val ownerId = classId.outerClassId + if (classId.isLocal) { + ownerId?.asLocal() ?: classId + } else { + ownerId + } + } + is FirCallableSymbol<*> -> callableId.classId + else -> error("Unsupported owner search for ${fir.javaClass}: ${fir.render()}") + } + } + + private fun ClassId.asLocal(): ClassId = ClassId(packageFqName, relativeClassName, true) + + protected fun canSeeProtectedMemberOf( + containingDeclarationOfUseSite: List, + dispatchReceiver: ReceiverValue?, + ownerId: ClassId, session: FirSession + ): Boolean { + if (canSeePrivateMemberOf(containingDeclarationOfUseSite, ownerId, session)) return true + + for (containingDeclaration in containingDeclarationOfUseSite) { + if (containingDeclaration !is FirClass<*>) continue + val boundSymbol = containingDeclaration.symbol + if (canSeeProtectedMemberOf(boundSymbol.fir, dispatchReceiver, ownerId, session)) return true + } + + return false + } + + protected fun AbstractFirBasedSymbol<*>.packageFqName(): FqName { + return when (this) { + is FirClassLikeSymbol<*> -> classId.packageFqName + is FirCallableSymbol<*> -> callableId.packageName + else -> error("No package fq name for $this") + } + } +} + +val FirSession.visibilityChecker: FirVisibilityChecker by FirSession.sessionComponentAccessor() diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt index 5f5dd7430c1..1a8ff587437 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/calls/ResolutionStages.kt @@ -5,8 +5,7 @@ package org.jetbrains.kotlin.fir.resolve.calls -import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.Visibilities +import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.diagnostics.ConeSimpleDiagnostic import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind @@ -14,7 +13,6 @@ import org.jetbrains.kotlin.fir.expressions.FirExpression import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression import org.jetbrains.kotlin.fir.expressions.FirResolvedQualifier import org.jetbrains.kotlin.fir.references.FirSuperReference -import org.jetbrains.kotlin.fir.render import org.jetbrains.kotlin.fir.resolve.* import org.jetbrains.kotlin.fir.resolve.inference.* import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol @@ -23,7 +21,6 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol -import org.jetbrains.kotlin.fir.typeContext import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.load.java.JavaVisibilities import org.jetbrains.kotlin.name.ClassId @@ -375,10 +372,11 @@ internal object DiscriminateSynthetics : CheckerStage() { internal object CheckVisibility : CheckerStage() { override suspend fun check(candidate: Candidate, sink: CheckerSink, callInfo: CallInfo) { + val visibilityChecker = callInfo.session.visibilityChecker val symbol = candidate.symbol val declaration = symbol.fir if (declaration is FirMemberDeclaration) { - if (!checkVisibility(declaration, symbol, sink, candidate)) { + if (!checkVisibility(declaration, symbol, sink, candidate, visibilityChecker)) { return } } @@ -392,173 +390,24 @@ internal object CheckVisibility : CheckerStage() { if (classSymbol.fir.classKind.isSingleton) { sink.yieldApplicability(CandidateApplicability.HIDDEN) } - checkVisibility(classSymbol.fir, classSymbol, sink, candidate) + checkVisibility(classSymbol.fir, classSymbol, sink, candidate, visibilityChecker) } } } - private fun canSeePrivateMemberOf( - containingDeclarationOfUseSite: List, - ownerId: ClassId, - session: FirSession - ): Boolean { - ownerId.ownerIfCompanion(session)?.let { companionOwnerClassId -> - return canSeePrivateMemberOf(containingDeclarationOfUseSite, companionOwnerClassId, session) - } - - for (declaration in containingDeclarationOfUseSite) { - if (declaration !is FirClass<*>) continue - val boundSymbol = declaration.symbol - if (boundSymbol.classId.isSame(ownerId)) { - return true - } - } - - return false - } - - // 'local' isn't taken into account here - private fun ClassId.isSame(other: ClassId): Boolean = - packageFqName == other.packageFqName && relativeClassName == other.relativeClassName - - private fun ClassId.ownerIfCompanion(session: FirSession): ClassId? { - if (outerClassId == null || isLocal) return null - val ownerSymbol = session.firSymbolProvider.getClassLikeSymbolByFqName(this) as? FirRegularClassSymbol - - return outerClassId.takeIf { ownerSymbol?.fir?.isCompanion == true } - } - - private fun canSeeProtectedMemberOf( - containingUseSiteClass: FirClass<*>, - dispatchReceiver: ReceiverValue?, - ownerId: ClassId, session: FirSession - ): Boolean { - dispatchReceiver?.ownerIfCompanion(session)?.let { companionOwnerClassId -> - if (containingUseSiteClass.isSubClass(companionOwnerClassId, session)) return true - } - - // TODO: Add check for receiver, see org.jetbrains.kotlin.descriptors.Visibility#doesReceiverFitForProtectedVisibility - return containingUseSiteClass.isSubClass(ownerId, session) - } - - private fun FirClass<*>.isSubClass(ownerId: ClassId, session: FirSession): Boolean { - if (classId.isSame(ownerId)) return true - - return lookupSuperTypes(this, lookupInterfaces = true, deep = true, session).any { superType -> - (superType as? ConeClassLikeType)?.fullyExpandedType(session)?.lookupTag?.classId?.isSame(ownerId) == true - } - } - - private fun ReceiverValue?.ownerIfCompanion(session: FirSession): ClassId? = - (this?.type as? ConeClassLikeType)?.lookupTag?.classId?.ownerIfCompanion(session) - private suspend fun checkVisibility( declaration: FirMemberDeclaration, symbol: AbstractFirBasedSymbol<*>, sink: CheckerSink, - candidate: Candidate + candidate: Candidate, + visibilityChecker: FirVisibilityChecker ): Boolean { - val callInfo = candidate.callInfo - val useSiteFile = callInfo.containingFile - val containingDeclarations = callInfo.containingDeclarations - val session = callInfo.session - val provider = session.firProvider - val ownerId = symbol.getOwnerId() - val visible = when (declaration.visibility) { - JavaVisibilities.PACKAGE_VISIBILITY -> { - symbol.packageFqName() == useSiteFile.packageFqName - } - Visibilities.Internal -> { - declaration.session == callInfo.session - } - Visibilities.Private, Visibilities.PrivateToThis -> { - if (declaration.session == callInfo.session) { - if (ownerId == null || declaration is FirConstructor && declaration.isFromSealedClass) { - val candidateFile = when (symbol) { - is FirClassLikeSymbol<*> -> provider.getFirClassifierContainerFileIfAny(symbol) - is FirCallableSymbol<*> -> provider.getFirCallableContainerFile(symbol) - else -> null - } - // Top-level: visible in file - candidateFile == useSiteFile - } else { - // Member: visible inside parent class, including all its member classes - canSeePrivateMemberOf(containingDeclarations, ownerId, session) - } - } else { - declaration is FirSimpleFunction && declaration.isAllowedToBeAccessedFromOutside() - } - } - Visibilities.Protected -> { - ownerId != null && canSeeProtectedMemberOf(containingDeclarations, candidate.dispatchReceiverValue, ownerId, session) - } - JavaVisibilities.PROTECTED_AND_PACKAGE, JavaVisibilities.PROTECTED_STATIC_VISIBILITY -> { - if (symbol.packageFqName() == useSiteFile.packageFqName) { - true - } else { - ownerId != null && canSeeProtectedMemberOf(containingDeclarations, candidate.dispatchReceiverValue, ownerId, session) - } - } - else -> true - } - - if (!visible) { + if (!visibilityChecker.isVisible(declaration, symbol, candidate)) { sink.yieldApplicability(CandidateApplicability.HIDDEN) return false } return true } - - // monitorEnter/monitorExit are the only functions which are accessed "illegally" (see kotlin/util/Synchronized.kt). - // Since they are intrinsified in the codegen, FIR should treat it as visible. - private fun FirSimpleFunction.isAllowedToBeAccessedFromOutside(): Boolean { - if (!isFromLibrary) return false - val packageName = symbol.callableId.packageName.asString() - val name = name.asString() - return packageName == "kotlin.jvm.internal.unsafe" && - (name == "monitorEnter" || name == "monitorExit") - } - - private fun AbstractFirBasedSymbol<*>.getOwnerId(): ClassId? { - return when (this) { - is FirClassLikeSymbol<*> -> { - val ownerId = classId.outerClassId - if (classId.isLocal) { - ownerId?.asLocal() ?: classId - } else { - ownerId - } - } - is FirCallableSymbol<*> -> callableId.classId - else -> error("Unsupported owner search for ${fir.javaClass}: ${fir.render()}") - } - } - - private fun ClassId.asLocal(): ClassId = ClassId(packageFqName, relativeClassName, true) - - private fun canSeeProtectedMemberOf( - containingDeclarationOfUseSite: List, - dispatchReceiver: ReceiverValue?, - ownerId: ClassId, session: FirSession - ): Boolean { - if (canSeePrivateMemberOf(containingDeclarationOfUseSite, ownerId, session)) return true - - for (containingDeclaration in containingDeclarationOfUseSite) { - if (containingDeclaration !is FirClass<*>) continue - val boundSymbol = containingDeclaration.symbol - if (canSeeProtectedMemberOf(boundSymbol.fir, dispatchReceiver, ownerId, session)) return true - } - - return false - } - - private fun AbstractFirBasedSymbol<*>.packageFqName(): FqName { - return when (this) { - is FirClassLikeSymbol<*> -> classId.packageFqName - is FirCallableSymbol<*> -> callableId.packageName - else -> error("No package fq name for $this") - } - } } internal object CheckLowPriorityInOverloadResolution : CheckerStage() { diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeJavaModuleBasedSession.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeJavaModuleBasedSession.kt index 5b87424388c..e1f977da614 100644 --- a/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeJavaModuleBasedSession.kt +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/src/org/jetbrains/kotlin/idea/fir/low/level/api/FirIdeJavaModuleBasedSession.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.extensions.BunchOfRegisteredExtensions import org.jetbrains.kotlin.fir.extensions.extensionService import org.jetbrains.kotlin.fir.extensions.registerExtensions import org.jetbrains.kotlin.fir.java.JavaSymbolProvider +import org.jetbrains.kotlin.fir.java.registerJavaVisibilityChecker import org.jetbrains.kotlin.fir.resolve.calls.jvm.registerJvmCallConflictResolverFactory import org.jetbrains.kotlin.fir.resolve.firProvider import org.jetbrains.kotlin.fir.resolve.firSymbolProvider @@ -90,6 +91,7 @@ internal class FirIdeJavaModuleBasedSession private constructor( ) as FirSymbolProvider ) registerJvmCallConflictResolverFactory() + registerJavaVisibilityChecker() extensionService.registerExtensions(BunchOfRegisteredExtensions.empty()) } }