From a02109d8578c2a1f183c988610ef8467a7d55c8a Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Sun, 16 Aug 2020 20:21:17 +0300 Subject: [PATCH] [FIR] Add `FirVisibilityChecker` session component This is needed because we have different visibility rules for different platform visibilities (e.g. protected and package private on JVM), so we need ability to configure visibility checks for different platforms --- .../fir/java/FirJavaModuleBasedSession.kt | 1 + .../fir/java/FirJavaVisibilityChecker.kt | 48 ++++ .../kotlin/fir/FirVisibilityChecker.kt | 206 ++++++++++++++++++ .../fir/resolve/calls/ResolutionStages.kt | 165 +------------- .../level/api/FirIdeJavaModuleBasedSession.kt | 2 + 5 files changed, 264 insertions(+), 158 deletions(-) create mode 100644 compiler/fir/java/src/org/jetbrains/kotlin/fir/java/FirJavaVisibilityChecker.kt create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/FirVisibilityChecker.kt 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()) } }