Skip to content

Commit

Permalink
[FIR] Propagate variables initialized in loops to parent statements
Browse files Browse the repository at this point in the history
When checking local variables initialization, there is a look-ahead pass
that finds variables declared within repeatable statements. This is
used to reset initialization information from certain control-flow graph
paths when calculating if a variable is correctly initialized. Loops
where not correctly propagating variables to outer repeatable
statements, which caused problems for certain nested structures with
jumps.

^KT-69494 Fixed

(cherry picked from commit b450bd4)
  • Loading branch information
bnorm authored and qodana-bot committed Jul 25, 2024
1 parent e9ad2a1 commit a1167e1
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 48 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -103,45 +103,7 @@ abstract class VariableInitializationCheckProcessor {
path: EdgeLabel,
visited: MutableSet<CFGNode<*>>,
) {
require(visited.add(this)) {
val problemNode = this@reportErrorsOnInitializationsInInputs
val containingDeclaration = problemNode.containingDeclaration()
buildString {
appendLine("Node has already been visited and could result in infinite recursion.")
appendLine()

appendLine("CFG")
append("- Node: ").appendLine(problemNode.render())
append("- Path: ").appendLine(path.label)
appendLine("- Visited:")
for (n in visited) {
append(" - ").appendLine(n.render())
}
appendLine()

appendLine("Context")
append("File Path: ").appendLine(context.containingFilePath)
appendLine("Declarations:")
for (d in context.containingDeclarations) {
append("- ").appendLine(d.symbol.fqName)
}
appendLine()

appendLine("Variable")
append("- FQName: ").appendLine(symbol.fqName)
append("- ElementKind: ").appendLine(symbol.source?.kind?.let { it::class.simpleName })
appendLine(symbol.source?.getElementTextInContextForDebug())
appendLine()

if (containingDeclaration != null) {
appendLine("Containing Declaration")
append("- FQName: ").appendLine(containingDeclaration.symbol.fqName)
append("- ElementKind: ").appendLine(containingDeclaration.source?.kind?.let { it::class.simpleName })
appendLine(containingDeclaration.source?.getElementTextInContextForDebug())
appendLine()
}
}
}
require(visited.add(this)) { buildRecursionErrorMessage(this, symbol, context) }

for (previousNode in previousCfgNodes) {
if (edgeFrom(previousNode).kind.isBack) continue
Expand Down Expand Up @@ -348,20 +310,41 @@ private val FirVariableSymbol<*>.isLocal: Boolean
else -> false
}

private fun CFGNode<*>.containingDeclaration(): FirDeclaration? {
fun buildRecursionErrorMessage(
problemNode: CFGNode<*>,
symbol: FirVariableSymbol<*>,
context: CheckerContext,
): String {
return buildString {
appendLine("Node has already been visited and could result in infinite recursion.")
appendLine()
append("File Path: ").appendLine(context.containingFilePath)
append("Variable: ").appendLine(symbol.getDebugFqName())
appendLine("Declarations:")
problemNode.firstGraphDeclaration()?.let { declaration ->
append("- ").append(declaration.symbol.getDebugFqName()).appendLine(" (graph declaration)")
}
for (declaration in context.containingDeclarations) {
append("- ").appendLine(declaration.symbol.getDebugFqName())
}
}
}

private fun CFGNode<*>.firstGraphDeclaration(): FirDeclaration? {
owner.declaration?.let { return it }
return owner.enterNode.previousNodes.firstNotNullOfOrNull { it.containingDeclaration() }
return owner.enterNode.previousNodes.firstNotNullOfOrNull { it.firstGraphDeclaration() }
}

@OptIn(SymbolInternals::class)
private val FirBasedSymbol<*>.fqName: FqName
get() = when (val fir = this.fir) {
private fun FirBasedSymbol<*>.getDebugFqName(): FqName {
return when (val fir = this.fir) {
is FirFile -> fir.packageFqName.child(Name.identifier(fir.name))
is FirScript -> fir.symbol.fqName
is FirClassLikeDeclaration -> fir.symbol.classId.asSingleFqName()
is FirTypeParameter -> fir.containingDeclarationSymbol.fqName.child(fir.name)
is FirAnonymousInitializer -> (fir.containingDeclarationSymbol?.fqName ?: FqName.ROOT).child(Name.special("<init>"))
is FirCallableDeclaration -> fir.symbol.callableId.asSingleFqName()
is FirTypeParameter -> fir.containingDeclarationSymbol.getDebugFqName().child(fir.name)
is FirAnonymousInitializer -> (fir.containingDeclarationSymbol?.getDebugFqName() ?: FqName.ROOT).child(Name.special("<init>"))
is FirCallableDeclaration -> fir.symbol.callableId.asFqNameForDebugInfo()
is FirCodeFragment -> FqName.topLevel(Name.special("<fragment>"))
is FirDanglingModifierList -> FqName.topLevel(Name.special("<dangling>"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,11 @@ private class PropertyDeclarationCollector(
}

override fun visitWhileLoop(whileLoop: FirWhileLoop, data: FirStatement?) {
visitRepeatable(whileLoop, whileLoop)
visitRepeatable(whileLoop, data)
}

override fun visitDoWhileLoop(doWhileLoop: FirDoWhileLoop, data: FirStatement?) {
visitRepeatable(doWhileLoop, doWhileLoop)
visitRepeatable(doWhileLoop, data)
}

override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: FirStatement?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
fun test(loop: Boolean) {
while (loop) {
try {
do {
run<Unit> {
val a: String
if (loop) {
a = ""
} else {
a = ""
}
}
} while (loop)
} catch (e: Exception) {
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
fun test(loop: Boolean) {
while (loop) {
try {
do {
run<Unit> {
val <!ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE!>a<!>: String
if (loop) {
<!UNUSED_VALUE!>a =<!> ""
} else {
<!UNUSED_VALUE!>a =<!> ""
}
}
} while (loop)
} catch (e: Exception) {
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit a1167e1

Please sign in to comment.