From 47d7c99af9836f17e9a02b2aebb05539432df508 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 28 Jan 2025 20:08:23 +0100 Subject: [PATCH 01/12] interprocedural follow next EOG edges until hit --- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 365d6bcac30..39112a54f1c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -284,9 +284,7 @@ fun Node.followPrevDFGEdgesUntilHit( // from). // We try to pop from the stack and only select the elements with the // matching index. - ctx.indexStack.popIfOnTop( - it.granularity as IndexedDataflowGranularity - ) == true + ctx.indexStack.popIfOnTop(it.granularity as IndexedDataflowGranularity) } else { true } @@ -327,7 +325,7 @@ class Context( val callStack: SimpleStack = SimpleStack(), ) { fun clone(): Context { - return Context(indexStack.clone(), callStack.clone()) + return Context(indexStack = indexStack.clone(), callStack = callStack.clone()) } } @@ -360,6 +358,9 @@ class SimpleStack { return false } + /** Pops the top element from the stack. */ + fun pop(): T = deque.removeFirst() + /** Clones the stack. */ fun clone(): SimpleStack { return SimpleStack().apply { deque.addAll(this@SimpleStack.deque) } @@ -798,11 +799,27 @@ fun Node.followNextDFGEdgesUntilHit( fun Node.followNextEOGEdgesUntilHit( collectFailedPaths: Boolean = true, findAllPossiblePaths: Boolean = true, + interproceduralAnalysis: Boolean = true, predicate: (Node) -> Boolean, ): FulfilledAndFailedPaths { return followXUntilHit( - x = { currentNode, _, _ -> - currentNode.nextEOGEdges.filter { it.unreachable != true }.map { it.end } + x = { currentNode, ctx, _ -> + if (interproceduralAnalysis && currentNode is CallExpression) { + ctx.callStack.push(currentNode) + currentNode.invokes.flatMap { it.eogStarters } + } else if (interproceduralAnalysis && currentNode is ReturnStatement) { + if (ctx.callStack.isEmpty()) { + (currentNode.firstParentOrNull { it is FunctionDeclaration } + as? FunctionDeclaration) + ?.usages + ?.mapNotNull { (it.astParent as? CallExpression)?.nextEOG } + ?.flatten() ?: setOf() + } else { + ctx.callStack.pop().nextEOG + } + } else { + currentNode.nextEOGEdges.filter { it.unreachable != true }.map { it.end } + } }, collectFailedPaths = collectFailedPaths, findAllPossiblePaths = findAllPossiblePaths, From e5ecd3d13cc563ef0fa192135917aa7d70df4ce7 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 29 Jan 2025 11:19:12 +0100 Subject: [PATCH 02/12] Fix some tests --- .../main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 39112a54f1c..b7277893c6c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -807,7 +807,11 @@ fun Node.followNextEOGEdgesUntilHit( if (interproceduralAnalysis && currentNode is CallExpression) { ctx.callStack.push(currentNode) currentNode.invokes.flatMap { it.eogStarters } - } else if (interproceduralAnalysis && currentNode is ReturnStatement) { + } else if ( + interproceduralAnalysis && + (currentNode is ReturnStatement || + currentNode is FunctionDeclaration && currentNode.nextEOG.isEmpty()) + ) { if (ctx.callStack.isEmpty()) { (currentNode.firstParentOrNull { it is FunctionDeclaration } as? FunctionDeclaration) From e18041010765081b6d0eef5993fe500e506ad39c Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 30 Jan 2025 16:05:46 +0100 Subject: [PATCH 03/12] Fix conditions for missing invokes and returns --- .../kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 58ad9b92374..a9fc05678d1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -805,13 +805,16 @@ fun Node.followNextEOGEdgesUntilHit( ): FulfilledAndFailedPaths { return followXUntilHit( x = { currentNode, ctx, _ -> - if (interproceduralAnalysis && currentNode is CallExpression) { + if ( + interproceduralAnalysis && + currentNode is CallExpression && + currentNode.invokes.isNotEmpty() + ) { ctx.callStack.push(currentNode) currentNode.invokes.flatMap { it.eogStarters } } else if ( interproceduralAnalysis && - (currentNode is ReturnStatement || - currentNode is FunctionDeclaration && currentNode.nextEOG.isEmpty()) + (currentNode is ReturnStatement || currentNode.nextEOG.isEmpty()) ) { if (ctx.callStack.isEmpty()) { (currentNode.firstParentOrNull { it is FunctionDeclaration } From 9b30c8a2472a4d976ca1d338b152714db9f1c3ff Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 30 Jan 2025 16:14:24 +0100 Subject: [PATCH 04/12] Fix another test --- .../aisec/cpg/graph/ShortcutsTest.kt | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt index 82c0723ec1a..e319c6fa214 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/ShortcutsTest.kt @@ -327,15 +327,33 @@ class ShortcutsTest { val ifCondition = ((magic.body as Block).statements[0] as IfStatement).condition as BinaryOperator - val paramPassed = - ifCondition.followNextEOGEdgesUntilHit { + // There are the following paths: + // - the else branch (which fulfills the requirement) + // - the then/then (fails) + // - the then/else (fails) + val paramPassedIntraproceduralOnly = + ifCondition.followNextEOGEdgesUntilHit(interproceduralAnalysis = false) { it is AssignExpression && it.operatorCode == "=" && (it.rhs.first() as? Reference)?.refersTo == (ifCondition.lhs as Reference).refersTo } - assertEquals(1, paramPassed.fulfilled.size) - assertEquals(2, paramPassed.failed.size) + assertEquals(1, paramPassedIntraproceduralOnly.fulfilled.size) + assertEquals(2, paramPassedIntraproceduralOnly.failed.size) + + // There are the following paths: + // - the else branch (which fulfills the requirement) + // - the then/then and 3 paths when we enter magic2 through this path (=> 3 fails) + // - the then/else and 3 paths when we enter magic2 through this path (=> 3 fails) + val paramPassedInterprocedural = + ifCondition.followNextEOGEdgesUntilHit(interproceduralAnalysis = true) { + it is AssignExpression && + it.operatorCode == "=" && + (it.rhs.first() as? Reference)?.refersTo == + (ifCondition.lhs as Reference).refersTo + } + assertEquals(1, paramPassedInterprocedural.fulfilled.size) + assertEquals(6, paramPassedInterprocedural.failed.size) } @Test From a4d9bb7ee512a61b33c3dc0fa0e3085dc812460a Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 30 Jan 2025 16:31:56 +0100 Subject: [PATCH 05/12] Fix bug --- .../main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index a9fc05678d1..216bf5edd84 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -659,7 +659,7 @@ inline fun Node.followXUntilHit( (next !in alreadySeenNodes && worklist.none { next in it.first })) ) { val newContext = - if (nextPath.size > 1) { + if (nextNodes.size > 1) { currentContext.clone() } else { currentContext @@ -810,6 +810,8 @@ fun Node.followNextEOGEdgesUntilHit( currentNode is CallExpression && currentNode.invokes.isNotEmpty() ) { + // We follow the invokes edges and push the call expression on the call stack, so we + // can jump back here after processing the function. ctx.callStack.push(currentNode) currentNode.invokes.flatMap { it.eogStarters } } else if ( From 697595de1e132360e5f034397afce97d02a2d88e Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 30 Jan 2025 17:45:57 +0100 Subject: [PATCH 06/12] Integrate feature to follow prevEOG interprocedurally --- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 54 ++++++++++++++++--- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 216bf5edd84..27ab06f3f0a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -388,10 +388,14 @@ fun Node.collectAllNextFullDFGPaths(): List> { * Iterates the next EOG edges until there are no more edges available (or until a loop is * detected). Returns a list of possible paths (each path is represented by a list of nodes). */ -fun Node.collectAllNextEOGPaths(): List> { +fun Node.collectAllNextEOGPaths(interproceduralAnalysis: Boolean = true): List> { // We make everything fail to reach the end of the CDG. Then, we use the stuff collected in the // failed paths (everything) - return this.followNextEOGEdgesUntilHit(collectFailedPaths = true, findAllPossiblePaths = true) { + return this.followNextEOGEdgesUntilHit( + collectFailedPaths = true, + findAllPossiblePaths = true, + interproceduralAnalysis = interproceduralAnalysis, + ) { false } .failed @@ -821,9 +825,8 @@ fun Node.followNextEOGEdgesUntilHit( if (ctx.callStack.isEmpty()) { (currentNode.firstParentOrNull { it is FunctionDeclaration } as? FunctionDeclaration) - ?.usages - ?.mapNotNull { (it.astParent as? CallExpression)?.nextEOG } - ?.flatten() ?: setOf() + ?.callSites + ?.flatMap { it.nextEOG } ?: setOf() } else { ctx.callStack.pop().nextEOG } @@ -837,6 +840,15 @@ fun Node.followNextEOGEdgesUntilHit( ) } +/** Returns a collection of all [CallExpression]s which call this [FunctionDeclaration]. */ +val FunctionDeclaration.callSites: Collection + // TODO: This may not account for multiple invokes edges from one [CallExpression] but we have + // no reverse edge of invokes?! See issue 2011 + get() = this.usages.mapNotNull { it.astParent as? CallExpression } + +val FunctionDeclaration.lastEOGEdges: Collection + get() = collectAllNextEOGPaths(false).flatMap { it.last().prevEOGEdges } + /** * Returns an instance of [FulfilledAndFailedPaths] where [FulfilledAndFailedPaths.fulfilled] * contains all possible shortest evaluation paths between the end node [this] and the start node @@ -850,11 +862,39 @@ fun Node.followNextEOGEdgesUntilHit( fun Node.followPrevEOGEdgesUntilHit( collectFailedPaths: Boolean = true, findAllPossiblePaths: Boolean = true, + interproceduralAnalysis: Boolean = true, predicate: (Node) -> Boolean, ): FulfilledAndFailedPaths { + return followXUntilHit( - x = { currentNode, _, _ -> - currentNode.prevEOGEdges.filter { it.unreachable != true }.map { it.start } + x = { currentNode, ctx, _ -> + when { + interproceduralAnalysis && + currentNode is FunctionDeclaration && + ctx.callStack.isEmpty() -> { + // We're at the beginning of a function. If the stack is empty, we jump to + // all calls of this function. + currentNode.callSites.flatMap { it.prevEOGEdges } + } + interproceduralAnalysis && currentNode is FunctionDeclaration -> { + // We're at the beginning of a function. If there's something on the stack, + // we ended up here by following the respective call expression, and we jump + // back there. + ctx.callStack.pop().prevEOGEdges + } + interproceduralAnalysis && currentNode is CallExpression -> { + // We're in the call expression. Push it on the stack, go to all last EOG + // nodes in the functions which are invoked and continue there. + ctx.callStack.push(currentNode) + + currentNode.invokes.flatMap { it.lastEOGEdges } + } + else -> { + currentNode.prevEOGEdges + } + } + .filter { it.unreachable != true } + .map { it.start } }, collectFailedPaths = collectFailedPaths, findAllPossiblePaths = findAllPossiblePaths, From 727a3ff5cb93b2732a57dcbca3ceb49b6c043f54 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 30 Jan 2025 17:48:23 +0100 Subject: [PATCH 07/12] Fix if no invokes edges exist --- .../main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 27ab06f3f0a..8585b8227c9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -882,7 +882,9 @@ fun Node.followPrevEOGEdgesUntilHit( // back there. ctx.callStack.pop().prevEOGEdges } - interproceduralAnalysis && currentNode is CallExpression -> { + interproceduralAnalysis && + currentNode is CallExpression && + currentNode.invokes.isNotEmpty() -> { // We're in the call expression. Push it on the stack, go to all last EOG // nodes in the functions which are invoked and continue there. ctx.callStack.push(currentNode) From f7a52169cc9b3fc6bd3f39b6748a8253f443896b Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 30 Jan 2025 18:23:29 +0100 Subject: [PATCH 08/12] Fix another test --- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 8585b8227c9..32a15495d68 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -34,6 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.Block import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import kotlin.collections.filter import kotlin.collections.firstOrNull import kotlin.math.absoluteValue @@ -846,8 +847,18 @@ val FunctionDeclaration.callSites: Collection // no reverse edge of invokes?! See issue 2011 get() = this.usages.mapNotNull { it.astParent as? CallExpression } -val FunctionDeclaration.lastEOGEdges: Collection - get() = collectAllNextEOGPaths(false).flatMap { it.last().prevEOGEdges } +val FunctionDeclaration.lastEOGNode: Collection + get() { + val lastEOG = collectAllNextEOGPaths(false).flatMap { it.last().prevEOGEdges } + return if (lastEOG.isEmpty()) { + // In some cases, we have no body, so we have to jump directly to the function + // declaration. + listOf(this) + } else lastEOG.filter { it.unreachable != true }.map { it.start } + } + +val Node.reachablePrevEOG: Collection + get() = this.prevEOGEdges.filter { it.unreachable != true }.map { it.start } /** * Returns an instance of [FulfilledAndFailedPaths] where [FulfilledAndFailedPaths.fulfilled] @@ -869,34 +880,32 @@ fun Node.followPrevEOGEdgesUntilHit( return followXUntilHit( x = { currentNode, ctx, _ -> when { - interproceduralAnalysis && - currentNode is FunctionDeclaration && - ctx.callStack.isEmpty() -> { - // We're at the beginning of a function. If the stack is empty, we jump to - // all calls of this function. - currentNode.callSites.flatMap { it.prevEOGEdges } - } - interproceduralAnalysis && currentNode is FunctionDeclaration -> { - // We're at the beginning of a function. If there's something on the stack, - // we ended up here by following the respective call expression, and we jump - // back there. - ctx.callStack.pop().prevEOGEdges - } - interproceduralAnalysis && - currentNode is CallExpression && - currentNode.invokes.isNotEmpty() -> { - // We're in the call expression. Push it on the stack, go to all last EOG - // nodes in the functions which are invoked and continue there. - ctx.callStack.push(currentNode) - - currentNode.invokes.flatMap { it.lastEOGEdges } - } - else -> { - currentNode.prevEOGEdges - } + interproceduralAnalysis && + currentNode is FunctionDeclaration && + ctx.callStack.isEmpty() -> { + // We're at the beginning of a function. If the stack is empty, we jump to + // all calls of this function. + currentNode.callSites.flatMap { it.reachablePrevEOG } } - .filter { it.unreachable != true } - .map { it.start } + interproceduralAnalysis && currentNode is FunctionDeclaration -> { + // We're at the beginning of a function. If there's something on the stack, + // we ended up here by following the respective call expression, and we jump + // back there. + ctx.callStack.pop().reachablePrevEOG + } + interproceduralAnalysis && + currentNode is CallExpression && + currentNode.invokes.isNotEmpty() -> { + // We're in the call expression. Push it on the stack, go to all last EOG + // nodes in the functions which are invoked and continue there. + ctx.callStack.push(currentNode) + + currentNode.invokes.flatMap { it.lastEOGNode } + } + else -> { + currentNode.reachablePrevEOG + } + } }, collectFailedPaths = collectFailedPaths, findAllPossiblePaths = findAllPossiblePaths, From ededffda23e606674f5e83072b857041e7aead06 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 30 Jan 2025 19:21:08 +0100 Subject: [PATCH 09/12] Replace callSites with new calledBy edge --- .../kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 32a15495d68..060f4297d18 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -826,7 +826,7 @@ fun Node.followNextEOGEdgesUntilHit( if (ctx.callStack.isEmpty()) { (currentNode.firstParentOrNull { it is FunctionDeclaration } as? FunctionDeclaration) - ?.callSites + ?.calledBy ?.flatMap { it.nextEOG } ?: setOf() } else { ctx.callStack.pop().nextEOG @@ -841,12 +841,6 @@ fun Node.followNextEOGEdgesUntilHit( ) } -/** Returns a collection of all [CallExpression]s which call this [FunctionDeclaration]. */ -val FunctionDeclaration.callSites: Collection - // TODO: This may not account for multiple invokes edges from one [CallExpression] but we have - // no reverse edge of invokes?! See issue 2011 - get() = this.usages.mapNotNull { it.astParent as? CallExpression } - val FunctionDeclaration.lastEOGNode: Collection get() { val lastEOG = collectAllNextEOGPaths(false).flatMap { it.last().prevEOGEdges } @@ -885,7 +879,7 @@ fun Node.followPrevEOGEdgesUntilHit( ctx.callStack.isEmpty() -> { // We're at the beginning of a function. If the stack is empty, we jump to // all calls of this function. - currentNode.callSites.flatMap { it.reachablePrevEOG } + currentNode.calledBy.flatMap { it.reachablePrevEOG } } interproceduralAnalysis && currentNode is FunctionDeclaration -> { // We're at the beginning of a function. If there's something on the stack, From 29837628f367f82f294d2b94bdb97298cd6304a3 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 30 Jan 2025 19:23:01 +0100 Subject: [PATCH 10/12] document extensions --- .../main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 060f4297d18..e66412decd5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -841,6 +841,10 @@ fun Node.followNextEOGEdgesUntilHit( ) } +/** + * Returns the a [Collection] of last nodes in the EOG of this [FunctionDeclaration]. If there's no + * body, it will return a list of this function declaration. + */ val FunctionDeclaration.lastEOGNode: Collection get() { val lastEOG = collectAllNextEOGPaths(false).flatMap { it.last().prevEOGEdges } @@ -851,6 +855,7 @@ val FunctionDeclaration.lastEOGNode: Collection } else lastEOG.filter { it.unreachable != true }.map { it.start } } +/** Returns only potentially reachable previous EOG edges. */ val Node.reachablePrevEOG: Collection get() = this.prevEOGEdges.filter { it.unreachable != true }.map { it.start } From 90a1441cdce5ed07ae2a95b1448dad9b954b2a2b Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 31 Jan 2025 09:15:12 +0100 Subject: [PATCH 11/12] Refactor firstParentOrNull --- .../de/fraunhofer/aisec/cpg/ScopeManager.kt | 4 +--- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 18 ++++++++++-------- .../cpg/passes/EvaluationOrderGraphPass.kt | 6 ++++-- .../aisec/cpg/passes/inference/Inference.kt | 3 +-- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt index ad4b11a93c6..6c80422ad7e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/ScopeManager.kt @@ -93,9 +93,7 @@ class ScopeManager : ScopeProvider { /** The current block, according to the scope that is currently active. */ val currentBlock: Block? - get() = - currentScope?.astNode as? Block - ?: currentScope?.astNode?.firstParentOrNull { it is Block } as? Block + get() = currentScope?.astNode as? Block ?: currentScope?.astNode?.firstParentOrNull() /** * The current method in the active scope tree, this ensures that 'this' keywords are mapped diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index e66412decd5..2fa86b229b5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -824,8 +824,8 @@ fun Node.followNextEOGEdgesUntilHit( (currentNode is ReturnStatement || currentNode.nextEOG.isEmpty()) ) { if (ctx.callStack.isEmpty()) { - (currentNode.firstParentOrNull { it is FunctionDeclaration } - as? FunctionDeclaration) + (currentNode as? FunctionDeclaration + ?: currentNode.firstParentOrNull()) ?.calledBy ?.flatMap { it.nextEOG } ?: setOf() } else { @@ -849,8 +849,8 @@ val FunctionDeclaration.lastEOGNode: Collection get() { val lastEOG = collectAllNextEOGPaths(false).flatMap { it.last().prevEOGEdges } return if (lastEOG.isEmpty()) { - // In some cases, we have no body, so we have to jump directly to the function - // declaration. + // In some cases, we do not have a body, so we have to jump directly to the + // function declaration. listOf(this) } else lastEOG.filter { it.unreachable != true }.map { it.start } } @@ -1143,13 +1143,15 @@ val Node?.assigns: List * * @param predicate the search predicate */ -fun Node.firstParentOrNull(predicate: (Node) -> Boolean): Node? { +inline fun Node.firstParentOrNull( + noinline predicate: ((T) -> Boolean)? = null +): T? { // start at searchNodes parent - var node: Node? = this.astParent + var node = this.astParent while (node != null) { - if (predicate(node)) { + if (node is T && (predicate == null || predicate(node))) { return node } @@ -1348,7 +1350,7 @@ fun Expression?.unwrapReference(): Reference? { /** Returns the [TranslationUnitDeclaration] where this node is located in. */ val Node.translationUnit: TranslationUnitDeclaration? get() { - return firstParentOrNull { it is TranslationUnitDeclaration } as? TranslationUnitDeclaration + return firstParentOrNull() } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 22ec7f90f74..079e82f4fb3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -747,7 +747,9 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa } // Forwards all open and uncaught throwing nodes to the outer scope that may handle them val outerCatchingNode = - node.firstParentOrNull { parent -> parent is TryStatement || parent is LoopStatement } + node.firstParentOrNull { parent -> + parent is TryStatement || parent is LoopStatement + } if (outerCatchingNode != null) { // Forwarding is done by merging the currently associated throws to a type with the new // throws based on their type @@ -1311,7 +1313,7 @@ open class EvaluationOrderGraphPass(ctx: TranslationContext) : TranslationUnitPa if (throwType != null) { // Here, we identify the encapsulating ast node that can handle or relay a throw val handlingOrRelayingParent = - throwExpression.firstParentOrNull { parent -> + throwExpression.firstParentOrNull { parent -> parent is TryStatement || parent is FunctionDeclaration } if (handlingOrRelayingParent != null) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt index c9fd333c9ed..9a6404d4dc5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/inference/Inference.kt @@ -597,8 +597,7 @@ class Inference internal constructor(val start: Node, override val ctx: Translat } is ReturnStatement -> { // If this is part of a return statement, we can take the return type - val func = - hint.firstParentOrNull { it is FunctionDeclaration } as? FunctionDeclaration + val func = hint.firstParentOrNull() val returnTypes = func?.returnTypes return if (returnTypes != null && returnTypes.size > 1) { From 1e6479a7c76097ea5ef4be58ea05c8de59ec3135 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 31 Jan 2025 13:03:54 +0100 Subject: [PATCH 12/12] Review feedback --- .../kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index d891b797eff..c5a3b70adf0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -818,7 +818,7 @@ fun Node.followNextEOGEdgesUntilHit( // We follow the invokes edges and push the call expression on the call stack, so we // can jump back here after processing the function. ctx.callStack.push(currentNode) - currentNode.invokes.flatMap { it.eogStarters } + currentNode.invokes } else if ( interproceduralAnalysis && (currentNode is ReturnStatement || currentNode.nextEOG.isEmpty()) @@ -842,10 +842,10 @@ fun Node.followNextEOGEdgesUntilHit( } /** - * Returns the a [Collection] of last nodes in the EOG of this [FunctionDeclaration]. If there's no + * Returns a [Collection] of last nodes in the EOG of this [FunctionDeclaration]. If there's no * body, it will return a list of this function declaration. */ -val FunctionDeclaration.lastEOGNode: Collection +val FunctionDeclaration.lastEOGNodes: Collection get() { val lastEOG = collectAllNextEOGPaths(false).flatMap { it.last().prevEOGEdges } return if (lastEOG.isEmpty()) { @@ -899,7 +899,7 @@ fun Node.followPrevEOGEdgesUntilHit( // nodes in the functions which are invoked and continue there. ctx.callStack.push(currentNode) - currentNode.invokes.flatMap { it.lastEOGNode } + currentNode.invokes.flatMap { it.lastEOGNodes } } else -> { currentNode.reachablePrevEOG