[FIR] Create CFG for files to track top-level property initialization

In order to properly analyze top-level property initialization, a
control-flow graph must be created for FirFiles. This change adds the
foundation for the file CFG and updates body resolve to create the CFG.
Checking the CFG for proper initialization is separated into a following
change to ease code review.

KT-56683
This commit is contained in:
Brian Norman
2023-07-25 10:23:22 -05:00
committed by Space Team
parent 0b7eb32064
commit b55fda0c55
127 changed files with 19565 additions and 18746 deletions
@@ -42,7 +42,7 @@ sealed class LLFirResolveTarget(
abstract fun forEachTarget(action: (FirElementWithResolveState) -> Unit)
override fun toString(): String = buildString {
append(this::class.simpleName)
append(this@LLFirResolveTarget::class.simpleName)
append("(")
buildList {
add(firFile.name)
@@ -161,5 +161,5 @@ private fun KtNamedFunction.isReanalyzableContainer(): Boolean = hasBlockBody()
private fun KtPropertyAccessor.isReanalyzableContainer(): Boolean = isSetter || hasBlockBody() || property.typeReference != null
private fun KtProperty.isReanalyzableContainer(): Boolean = typeReference != null && (isTopLevel || !hasDelegateExpressionOrInitializer())
private fun KtProperty.isReanalyzableContainer(): Boolean = typeReference != null && !hasDelegateExpressionOrInitializer()
@@ -115,6 +115,8 @@ internal class FileStructure private constructor(
}
fun getAllDiagnosticsForFile(diagnosticCheckerFilter: DiagnosticCheckerFilter): Collection<KtPsiDiagnostic> {
// TODO, KT-60799: Add a new FileStructure for file diagnostics
firFile.lazyResolveToPhase(FirResolvePhase.BODY_RESOLVE)
val structureElements = getAllStructureElements()
return buildList {
collectDiagnosticsFromStructureElements(structureElements, diagnosticCheckerFilter)
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirResolveCont
import org.jetbrains.kotlin.fir.resolve.transformers.contracts.FirContractsDslNames
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.isUsedInControlFlowGraphBuilderForClass
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.isUsedInControlFlowGraphBuilderForFile
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.psi.KtCodeFragment
@@ -114,6 +115,19 @@ private class LLFirBodyTargetResolver(
return true
}
is FirFile -> {
if (target.resolvePhase >= resolverPhase) return true
resolveFileAnnotationContainerIfNeeded(target)
// resolve file CFG graph here, to do this we need to have property blocks resoled
resolveMembersForControlFlowGraph(target)
performCustomResolveUnderLock(target) {
calculateControlFlowGraph(target)
}
return true
}
is FirCodeFragment -> {
resolveCodeFragmentContext(target)
performCustomResolveUnderLock(target) {
@@ -156,6 +170,35 @@ private class LLFirBodyTargetResolver(
}
}
private fun calculateControlFlowGraph(target: FirFile) {
checkWithAttachment(
target.controlFlowGraphReference == null,
{ "'controlFlowGraphReference' should be 'null' if the file phase < $resolverPhase)" },
) {
withFirEntry("firFile", target)
}
val dataFlowAnalyzer = transformer.declarationsTransformer.dataFlowAnalyzer
dataFlowAnalyzer.enterFile(target, buildGraph = true)
val controlFlowGraph = dataFlowAnalyzer.exitFile()
?: errorWithAttachment("CFG should not be 'null' as 'buildGraph' is specified") {
withFirEntry("firFile", target)
}
target.replaceControlFlowGraphReference(FirControlFlowGraphReferenceImpl(controlFlowGraph))
}
private fun resolveMembersForControlFlowGraph(target: FirFile) {
withFile(target) {
for (member in target.declarations) {
if (member is FirControlFlowGraphOwner && member.isUsedInControlFlowGraphBuilderForFile) {
member.lazyResolveToPhase(resolverPhase.previous)
performResolve(member)
}
}
}
}
private fun resolveCodeFragmentContext(firCodeFragment: FirCodeFragment) {
val ktCodeFragment = firCodeFragment.psi as? KtCodeFragment
?: errorWithAttachment("Code fragment source not found") {
@@ -191,7 +234,7 @@ private class LLFirBodyTargetResolver(
override fun doLazyResolveUnderLock(target: FirElementWithResolveState) {
when (target) {
is FirRegularClass, is FirCodeFragment -> error("Should have been resolved in ${::doResolveWithoutLock.name}")
is FirFile, is FirRegularClass, is FirCodeFragment -> error("Should have been resolved in ${::doResolveWithoutLock.name}")
is FirConstructor -> resolve(target, BodyStateKeepers.CONSTRUCTOR)
is FirFunction -> resolve(target, BodyStateKeepers.FUNCTION)
is FirProperty -> resolve(target, BodyStateKeepers.PROPERTY)
@@ -202,7 +245,6 @@ private class LLFirBodyTargetResolver(
is FirDanglingModifierList,
is FirFileAnnotationsContainer,
is FirTypeAlias,
is FirFile,
-> {
// No bodies here
}
@@ -190,6 +190,9 @@ var KtFile.originalKtFile by UserDataProperty(ORIGINAL_KT_FILE_KEY)
private fun KtClassLikeDeclaration.findFir(provider: FirProvider): FirClassLikeDeclaration? {
// TODO, KTIJ-26848: this is a workaround for IDE index inconsistency for unnamed classes
if (name == null) return null
return if (provider is LLFirProvider) {
provider.getFirClassifierByDeclaration(this)
} else {
@@ -6,4 +6,4 @@ var x: Int/* ReanalyzablePropertyStructureElement */
val y = 42/* NonReanalyzableNonClassDeclarationStructureElement */
var z: Int = 15/* ReanalyzablePropertyStructureElement */
var z: Int = 15/* NonReanalyzableNonClassDeclarationStructureElement */
@@ -4,7 +4,7 @@ fun () {/* ReanalyzableFunctionStructureElement */
}
val : Int = 4/* ReanalyzablePropertyStructureElement */
val : Int = 4/* NonReanalyzableNonClassDeclarationStructureElement */
var : Int/* ReanalyzablePropertyStructureElement */
get() = 4
@@ -1,7 +1 @@
BEFORE MODIFICATION:
public final [ResolvedTo(BODY_RESOLVE)] val i: R|kotlin/Int| = Int(42)
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
AFTER MODIFICATION:
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val i: R|kotlin/Int| = LAZY_EXPRESSION
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
@@ -1,18 +1 @@
BEFORE MODIFICATION:
public final [ResolvedTo(BODY_RESOLVE)] val x: R|kotlin/Int|by <Unresolved name: lazy>#(<L> = [ResolvedTo(BODY_RESOLVE)] lazy@fun <anonymous>(): R|kotlin/Int| <inline=Unknown> {
local final [ResolvedTo(BODY_RESOLVE)] fun doSmth([ResolvedTo(BODY_RESOLVE)] i: R|kotlin/String|): R|kotlin/Int| {
^doSmth Int(4)
}
^ R|<local>/doSmth|(String(str))
}
)
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
^ D|/x|.<Unresolved name: getValue>#(Null(null), ::R|/x|)
}
AFTER MODIFICATION:
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val x: R|kotlin/Int|by LAZY_EXPRESSION
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
^ D|/x|.<Unresolved name: getValue>#(Null(null), ::R|/x|)
}
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
@@ -1,18 +1 @@
BEFORE MODIFICATION:
public final [ResolvedTo(BODY_RESOLVE)] val <no name provided>: R|kotlin/Int|by <Unresolved name: lazy>#(<L> = [ResolvedTo(BODY_RESOLVE)] lazy@fun <anonymous>(): R|kotlin/Int| <inline=Unknown> {
local final [ResolvedTo(BODY_RESOLVE)] fun doSmth([ResolvedTo(BODY_RESOLVE)] i: R|kotlin/String|): R|kotlin/Int| {
^doSmth Int(4)
}
^ R|<local>/doSmth|(String(str))
}
)
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
^ D|/<no name provided>|.<Unresolved name: getValue>#(Null(null), ::R|/<no name provided>|)
}
AFTER MODIFICATION:
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val <no name provided>: R|kotlin/Int|by LAZY_EXPRESSION
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
^ D|/<no name provided>|.<Unresolved name: getValue>#(Null(null), ::R|/<no name provided>|)
}
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
@@ -1,11 +1 @@
BEFORE MODIFICATION:
public final [ResolvedTo(BODY_RESOLVE)] val x: R|kotlin/Int|by <Unresolved name: ErrorDelegate>#()
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
^ D|/x|.<Unresolved name: getValue>#(Null(null), ::R|/x|)
}
AFTER MODIFICATION:
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val x: R|kotlin/Int|by LAZY_EXPRESSION
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int| {
^ D|/x|.<Unresolved name: getValue>#(Null(null), ::R|/x|)
}
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
@@ -1,7 +1 @@
BEFORE MODIFICATION:
public final [ResolvedTo(BODY_RESOLVE)] val x: R|kotlin/Int| = R|/doSmth|(String(str))
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
AFTER MODIFICATION:
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val x: R|kotlin/Int| = LAZY_EXPRESSION
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
@@ -1,7 +1 @@
BEFORE MODIFICATION:
public final [ResolvedTo(BODY_RESOLVE)] val <no name provided>: R|kotlin/Int| = R|/doSmth|(String(str))
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
AFTER MODIFICATION:
public final [ResolvedTo(ANNOTATIONS_ARGUMENTS_MAPPING)] val <no name provided>: R|kotlin/Int| = LAZY_EXPRESSION
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
IN-BLOCK MODIFICATION IS NOT APPLICABLE FOR THIS PLACE
@@ -480,12 +480,12 @@ FILE: [ResolvedTo(BODY_RESOLVE)] fileElements.kt
public [ResolvedTo(STATUS)] [ContainingClassKey=Anno] get(): R|kotlin/Int|
}
public? final? [ResolvedTo(RAW_FIR)] val a: <implicit> = LAZY_EXPRESSION
public? [ResolvedTo(RAW_FIR)] get(): <implicit>
public? final? [ResolvedTo(RAW_FIR)] val b: <implicit> = LAZY_EXPRESSION
public? [ResolvedTo(RAW_FIR)] get(): <implicit>
public? final? [ResolvedTo(RAW_FIR)] val c: <implicit> = LAZY_EXPRESSION
public? [ResolvedTo(RAW_FIR)] get(): <implicit>
public final [ResolvedTo(BODY_RESOLVE)] val a: R|kotlin/Int| = Int(1)
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
public final [ResolvedTo(BODY_RESOLVE)] val b: R|kotlin/Int| = Int(2).R|kotlin/Int.plus|(R|one/a|)
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
public final [ResolvedTo(BODY_RESOLVE)] val c: R|kotlin/Int| = R|one/b|.R|kotlin/Int.plus|(R|one/a|)
public [ResolvedTo(BODY_RESOLVE)] get(): R|kotlin/Int|
public? final? [ResolvedTo(RAW_FIR)] fun test1(): R|kotlin/Unit| { LAZY_BLOCK }
@Deprecated[Unresolved](String()) @Anno[Unresolved](IntegerLiteral(2)) public? final? [ResolvedTo(RAW_FIR)] fun test2(): R|kotlin/Unit| { LAZY_BLOCK }
public? final? [ResolvedTo(RAW_FIR)] class A : R|kotlin/Any| {