From ef8b7e15cc3dacd5f539abcc3d7c24f8f6551465 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 12 Jun 2026 19:18:50 +0200 Subject: [PATCH 01/13] Extract HasLocal from Expression --- .../fraunhofer/aisec/cpg/graph/Interfaces.kt | 17 ++++++++ .../aisec/cpg/graph/expressions/Block.kt | 31 ++++++++++++-- .../cpg/graph/expressions/Comprehension.kt | 32 +++++++++++++-- .../graph/expressions/DeclarationStatement.kt | 3 +- .../aisec/cpg/graph/expressions/DoWhile.kt | 33 +++++++++++++-- .../aisec/cpg/graph/expressions/Expression.kt | 40 ++----------------- .../cpg/graph/expressions/ExpressionList.kt | 10 ++++- .../aisec/cpg/graph/expressions/For.kt | 27 ++++++++++++- .../aisec/cpg/graph/expressions/ForEach.kt | 31 ++++++++++++-- .../aisec/cpg/graph/expressions/Switch.kt | 30 +++++++++++++- .../aisec/cpg/graph/expressions/Try.kt | 38 ++++++++++++++++-- .../aisec/cpg/graph/expressions/While.kt | 32 +++++++++++++-- 12 files changed, 263 insertions(+), 61 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Interfaces.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Interfaces.kt index 5b78faeeb65..8d1ea3ade17 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Interfaces.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Interfaces.kt @@ -29,8 +29,11 @@ import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.declarations.Method import de.fraunhofer.aisec.cpg.graph.declarations.Operator +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.Variable import de.fraunhofer.aisec.cpg.graph.edges.MemoryAddressEdges +import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdge +import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdges import de.fraunhofer.aisec.cpg.graph.edges.flows.Dataflows import de.fraunhofer.aisec.cpg.graph.edges.flows.FullDataflowGranularity import de.fraunhofer.aisec.cpg.graph.expressions.BinaryOperator @@ -45,8 +48,22 @@ import de.fraunhofer.aisec.cpg.passes.DFGPass import de.fraunhofer.aisec.cpg.passes.PointsToPass import de.fraunhofer.aisec.cpg.passes.SymbolResolver import de.fraunhofer.aisec.cpg.persistence.DoNotPersist +import de.fraunhofer.aisec.cpg.persistence.Relationship import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation +interface HasLocals { + /** + * A list of local variables (or other values) associated to this statement, defined by their + * [ValueDeclaration] extracted from Block because `for`, `while`, `if`, and `switch` can + * declare locals in their condition or initializers. + */ + @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) + var localEdges: AstEdges> + + /** Virtual property to access [localEdges] without property edges. */ + var locals: MutableList +} + /** * Represents that this node (potentially) makes use of the given memory addresses e.g. to load or * store data. diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Block.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Block.kt index 816fc570191..9dae5fd6c63 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Block.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Block.kt @@ -25,10 +25,16 @@ */ package de.fraunhofer.aisec.cpg.graph.expressions +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder +import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.StatementHolder +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.Function +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.Variable import de.fraunhofer.aisec.cpg.graph.edges.Edge +import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.persistence.Relationship @@ -39,7 +45,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * A statement which contains a list of statements. A common example is a function body within a * [Function]. */ -open class Block : Expression(false), StatementHolder { +open class Block : Expression(false), StatementHolder, DeclarationHolder, HasLocals { /** The list of statements. */ @Relationship(value = "STATEMENTS", direction = Relationship.Direction.OUTGOING) @@ -61,10 +67,12 @@ open class Block : Expression(false), StatementHolder { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Block) return false - return super.equals(other) && Edge.propertyEqualsList(statementEdges, other.statementEdges) + return super.equals(other) && + Edge.propertyEqualsList(statementEdges, other.statementEdges) && + propertyEqualsList(localEdges, other.localEdges) } - override fun hashCode() = Objects.hash(super.hashCode()) + override fun hashCode() = Objects.hash(super.hashCode(), locals) /** Returns the [n]-th statement in this list of statements. */ operator fun get(n: Int): Expression { @@ -74,4 +82,21 @@ open class Block : Expression(false), StatementHolder { override fun getStartingPrevEOG(): Collection { return this.statements.firstOrNull()?.getStartingPrevEOG() ?: this.prevEOG } + + @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) + override var localEdges = astEdgesOf() + + /** Virtual property to access [localEdges] without property edges. */ + override var locals by unwrapping(Block::localEdges) + + override fun addDeclaration(declaration: Declaration) { + if (declaration is Variable) { + addIfNotContains(localEdges, declaration) + } else if (declaration is Function) { + addIfNotContains(localEdges, declaration) + } + } + + override val declarations: List + get() = locals } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Comprehension.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Comprehension.kt index 9ac203e79b3..3163654788c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Comprehension.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Comprehension.kt @@ -27,9 +27,17 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.AccessValues import de.fraunhofer.aisec.cpg.graph.ArgumentHolder +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder +import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.allChildren +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.Function +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.Variable +import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgeOf +import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.persistence.Relationship @@ -37,7 +45,7 @@ import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder /** This class holds the variable, iterable and predicate of the [CollectionComprehension]. */ -class Comprehension : Expression(), ArgumentHolder { +class Comprehension : Expression(), ArgumentHolder, DeclarationHolder, HasLocals { @Relationship("VARIABLE") var variableEdge = astEdgeOf( @@ -80,10 +88,11 @@ class Comprehension : Expression(), ArgumentHolder { return super.equals(other) && variable == other.variable && iterable == other.iterable && - predicate == other.predicate + predicate == other.predicate && + propertyEqualsList(localEdges, other.localEdges) } - override fun hashCode() = Objects.hash(super.hashCode(), variable, iterable, predicate) + override fun hashCode() = Objects.hash(super.hashCode(), variable, iterable, predicate, locals) override fun addArgument(expression: Expression) { if (this.variable is ProblemExpression) { @@ -126,4 +135,21 @@ class Comprehension : Expression(), ArgumentHolder { override fun getExitNextEOG(): Collection { return this.nextEOG.filter { it !in iterable.allChildren { true } } } + + @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) + override var localEdges = astEdgesOf() + + /** Virtual property to access [localEdges] without property edges. */ + override var locals by unwrapping(Comprehension::localEdges) + + override fun addDeclaration(declaration: Declaration) { + if (declaration is Variable) { + addIfNotContains(localEdges, declaration) + } else if (declaration is Function) { + addIfNotContains(localEdges, declaration) + } + } + + override val declarations: List + get() = locals } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DeclarationStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DeclarationStatement.kt index 1e586c87465..9b736401e0e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DeclarationStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DeclarationStatement.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.expressions +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList @@ -40,7 +41,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * contain other statements, but not declarations. Therefore, declarations are wrapped in a * [DeclarationStatement]. */ -open class DeclarationStatement : Expression(false) { +open class DeclarationStatement : Expression(false), DeclarationHolder { /** * The list of declarations declared or defined by this statement. It is always a list, even if diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DoWhile.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DoWhile.kt index e5d798d28ec..31572dad428 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DoWhile.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DoWhile.kt @@ -26,8 +26,16 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.ArgumentHolder +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder +import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.allChildren +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.Function +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.Variable +import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList +import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.persistence.Relationship @@ -38,7 +46,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * Represents a conditional loop statement of the form: `do{...}while(...)`. Where the body, usually * a [Block], is executed and re-executed if the [condition] evaluates to true. */ -class DoWhile : Loop(), ArgumentHolder { +class DoWhile : Loop(), ArgumentHolder, DeclarationHolder, HasLocals { @Relationship("CONDITION") var conditionEdge = astOptionalEdgeOf() /** * The loop condition that is evaluated after the loop statement and may trigger reevaluation. @@ -70,10 +78,12 @@ class DoWhile : Loop(), ArgumentHolder { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is DoWhile) return false - return super.equals(other) && condition == other.condition + return super.equals(other) && + condition == other.condition && + propertyEqualsList(localEdges, other.localEdges) } - override fun hashCode() = Objects.hash(super.hashCode(), condition) + override fun hashCode() = Objects.hash(super.hashCode(), condition, locals) override fun getStartingPrevEOG(): Collection { return statement?.getStartingPrevEOG()?.filter { it != this } @@ -84,4 +94,21 @@ class DoWhile : Loop(), ArgumentHolder { override fun getExitNextEOG(): Collection { return this.nextEOG.filter { it !in statement.allChildren { true } } } + + @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) + override var localEdges = astEdgesOf() + + /** Virtual property to access [localEdges] without property edges. */ + override var locals by unwrapping(DoWhile::localEdges) + + override fun addDeclaration(declaration: Declaration) { + if (declaration is Variable) { + addIfNotContains(localEdges, declaration) + } else if (declaration is Function) { + addIfNotContains(localEdges, declaration) + } + } + + override val declarations: List + get() = locals } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Expression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Expression.kt index dacfa8a192e..ed47916dec6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Expression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Expression.kt @@ -30,14 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.UnknownLanguage import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.AccessValues import de.fraunhofer.aisec.cpg.graph.AstNode -import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.declarations.Function -import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration -import de.fraunhofer.aisec.cpg.graph.declarations.Variable -import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList -import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.flows.Dataflows import de.fraunhofer.aisec.cpg.graph.edges.memoryAddressEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping @@ -62,7 +55,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * executable code. */ abstract class Expression(usedAsExpression: Boolean = true) : - AstNode(), DeclarationHolder, HasType, HasMemoryAddress, HasMemoryValue { + AstNode(), HasType, HasMemoryAddress, HasMemoryValue { /** * This property specifies that this node is used as an expression. Depending on the language, @@ -84,19 +77,6 @@ abstract class Expression(usedAsExpression: Boolean = true) : */ open var access: AccessValues = AccessValues.READ - /** - * A list of local variables (or other values) associated to this statement, defined by their - * [ValueDeclaration] extracted from Block because `for`, `while`, `if`, and `switch` can - * declare locals in their condition or initializers. - * - * TODO: This is actually an AST node just for a subset of nodes, i.e. initializers in for-loops - */ - @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) - var localEdges = astEdgesOf() - - /** Virtual property to access [localEdges] without property edges. */ - var locals by unwrapping(Expression::localEdges) - @DoNotPersist override val typeObservers: MutableSet = identitySetOf() override var language: Language<*> = UnknownLanguage @@ -168,22 +148,8 @@ abstract class Expression(usedAsExpression: Boolean = true) : override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Expression) return false - return super.equals(other) && - locals == other.locals && - propertyEqualsList(localEdges, other.localEdges) && - type == other.type - } - - override fun hashCode() = Objects.hash(super.hashCode(), locals) - - override fun addDeclaration(declaration: Declaration) { - if (declaration is Variable) { - addIfNotContains(localEdges, declaration) - } else if (declaration is Function) { - addIfNotContains(localEdges, declaration) - } + return super.equals(other) && type == other.type } - override val declarations: List - get() = locals + override fun hashCode() = super.hashCode() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ExpressionList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ExpressionList.kt index be0250049c4..336bdc8a100 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ExpressionList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ExpressionList.kt @@ -25,14 +25,16 @@ */ package de.fraunhofer.aisec.cpg.graph.expressions +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.persistence.Relationship import java.util.* -class ExpressionList : Expression() { +class ExpressionList : Expression(), DeclarationHolder { @Relationship(value = "SUBEXPR", direction = Relationship.Direction.OUTGOING) var expressionEdges = astEdgesOf() var expressions by unwrapping(ExpressionList::expressionEdges) @@ -56,4 +58,10 @@ class ExpressionList : Expression() { override fun getStartingPrevEOG(): Collection { return this.expressions.firstOrNull()?.getStartingPrevEOG() ?: this.prevEOG } + + override fun addDeclaration(declaration: Declaration) { + declarations.add(declaration) + } + + override val declarations: MutableList = mutableListOf() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/For.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/For.kt index 2a66172c01a..d9fb8208ad1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/For.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/For.kt @@ -27,6 +27,10 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.Function +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.Variable +import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdge import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdges import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf @@ -41,7 +45,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * that declares variables, can change them in an iteration statement and is executed until the * condition evaluates to false. */ -class For : Loop(), BranchingNode, StatementHolder { +class For : Loop(), BranchingNode, StatementHolder, DeclarationHolder, HasLocals { @Relationship("INITIALIZER_STATEMENT") var initializerStatementEdge = astOptionalEdgeOf() @@ -99,7 +103,8 @@ class For : Loop(), BranchingNode, StatementHolder { initializerStatement == other.initializerStatement && conditionDeclaration == other.conditionDeclaration && condition == other.condition && - iterationStatement == other.iterationStatement + iterationStatement == other.iterationStatement && + propertyEqualsList(localEdges, other.localEdges) } override fun hashCode(): Int { @@ -108,6 +113,7 @@ class For : Loop(), BranchingNode, StatementHolder { this.initializerStatement, this.conditionDeclaration, this.iterationStatement, + locals, ) } @@ -122,4 +128,21 @@ class For : Loop(), BranchingNode, StatementHolder { override fun getExitNextEOG(): Collection { return this.nextEOG.filter { it !in statement.allChildren { true } } } + + @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) + override var localEdges = astEdgesOf() + + /** Virtual property to access [localEdges] without property edges. */ + override var locals by unwrapping(For::localEdges) + + override fun addDeclaration(declaration: Declaration) { + if (declaration is Variable) { + addIfNotContains(localEdges, declaration) + } else if (declaration is Function) { + addIfNotContains(localEdges, declaration) + } + } + + override val declarations: List + get() = locals } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ForEach.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ForEach.kt index edf294f8ba5..2aedb29278a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ForEach.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ForEach.kt @@ -26,6 +26,11 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.Function +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.Variable +import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdge import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdges import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf @@ -39,7 +44,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * Represent a for statement of the form `for(variable ... iterable){...}` that executes the loop * body for each instance of an element in `iterable` that is temporarily stored in `variable`. */ -class ForEach : Loop(), BranchingNode, StatementHolder { +class ForEach : Loop(), BranchingNode, StatementHolder, DeclarationHolder, HasLocals { @Relationship("VARIABLE") var variableEdge = @@ -93,10 +98,13 @@ class ForEach : Loop(), BranchingNode, StatementHolder { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ForEach) return false - return super.equals(other) && variable == other.variable && iterable == other.iterable + return super.equals(other) && + variable == other.variable && + iterable == other.iterable && + propertyEqualsList(localEdges, other.localEdges) } - override fun hashCode() = Objects.hash(super.hashCode(), variable, iterable) + override fun hashCode() = Objects.hash(super.hashCode(), variable, iterable, locals) override fun getStartingPrevEOG(): Collection { val astChildren = this.allChildren { true } @@ -108,4 +116,21 @@ class ForEach : Loop(), BranchingNode, StatementHolder { override fun getExitNextEOG(): Collection { return this.nextEOG.filter { it !in statement.allChildren { true } } } + + @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) + override var localEdges = astEdgesOf() + + /** Virtual property to access [localEdges] without property edges. */ + override var locals by unwrapping(ForEach::localEdges) + + override fun addDeclaration(declaration: Declaration) { + if (declaration is Variable) { + addIfNotContains(localEdges, declaration) + } else if (declaration is Function) { + addIfNotContains(localEdges, declaration) + } + } + + override val declarations: List + get() = locals } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Switch.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Switch.kt index 4e577eb49db..ba77890ed7b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Switch.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Switch.kt @@ -26,8 +26,15 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder +import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.Function +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.Variable +import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList +import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.persistence.Relationship @@ -38,7 +45,7 @@ import java.util.Objects * and default statements. Break statements break out of the switch and labeled breaks in Java are * handled properly. */ -class Switch : Expression(false), BranchingNode { +class Switch : Expression(false), BranchingNode, DeclarationHolder, HasLocals { @Relationship(value = "SELECTOR") var selectorEdge = astOptionalEdgeOf() /** Selector that determines the case/default statement of the subsequent execution */ @@ -71,7 +78,8 @@ class Switch : Expression(false), BranchingNode { initializerStatement == other.initializerStatement && selectorDeclaration == other.selectorDeclaration && selector == other.selector && - statement == other.statement + statement == other.statement && + propertyEqualsList(localEdges, other.localEdges) } override fun hashCode() = @@ -81,6 +89,7 @@ class Switch : Expression(false), BranchingNode { selectorDeclaration, selector, statement, + locals, ) override fun getStartingPrevEOG(): Collection { @@ -93,4 +102,21 @@ class Switch : Expression(false), BranchingNode { override fun getExitNextEOG(): Collection { return this.statement?.getExitNextEOG() ?: this.nextEOG } + + @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) + override var localEdges = astEdgesOf() + + /** Virtual property to access [localEdges] without property edges. */ + override var locals by unwrapping(Switch::localEdges) + + override fun addDeclaration(declaration: Declaration) { + if (declaration is Variable) { + addIfNotContains(localEdges, declaration) + } else if (declaration is Function) { + addIfNotContains(localEdges, declaration) + } + } + + override val declarations: List + get() = locals } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Try.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Try.kt index 62957b779c6..9c71fee9590 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Try.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Try.kt @@ -25,7 +25,13 @@ */ package de.fraunhofer.aisec.cpg.graph.expressions +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder +import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.Function +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.Variable import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf @@ -34,7 +40,7 @@ import de.fraunhofer.aisec.cpg.persistence.Relationship import java.util.* /** A [Expression] which represents a try/catch block, primarily used for exception handling. */ -class Try : Expression(false) { +class Try : Expression(false), DeclarationHolder, HasLocals { /** * This represents some kind of resource which is typically opened (or similar) while entering @@ -88,11 +94,20 @@ class Try : Expression(false) { finallyBlock == other.finallyBlock && catchClauses == other.catchClauses && elseBlock == other.elseBlock && - propertyEqualsList(catchClauseEdges, other.catchClauseEdges)) + propertyEqualsList(catchClauseEdges, other.catchClauseEdges)) && + propertyEqualsList(localEdges, other.localEdges) } override fun hashCode() = - Objects.hash(super.hashCode(), resources, tryBlock, finallyBlock, catchClauses, elseBlock) + Objects.hash( + super.hashCode(), + resources, + tryBlock, + finallyBlock, + catchClauses, + elseBlock, + locals, + ) override fun getStartingPrevEOG(): Collection { return this.resources.firstOrNull()?.getStartingPrevEOG() @@ -100,4 +115,21 @@ class Try : Expression(false) { ?: this.finallyBlock?.getStartingPrevEOG() ?: this.prevEOG } + + @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) + override var localEdges = astEdgesOf() + + /** Virtual property to access [localEdges] without property edges. */ + override var locals by unwrapping(Try::localEdges) + + override fun addDeclaration(declaration: Declaration) { + if (declaration is Variable) { + addIfNotContains(localEdges, declaration) + } else if (declaration is Function) { + addIfNotContains(localEdges, declaration) + } + } + + override val declarations: List + get() = locals } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/While.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/While.kt index bcd50c32392..ab81c63f4e2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/While.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/While.kt @@ -28,9 +28,16 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.ArgumentHolder import de.fraunhofer.aisec.cpg.graph.AstNode import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder +import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.allChildren import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.declarations.Function +import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.Variable +import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList +import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.persistence.Relationship @@ -41,7 +48,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * Represents a conditional loop statement of the form: `while(...){...}`. The loop body is executed * until condition evaluates to false for the first time. */ -class While : Loop(), BranchingNode, ArgumentHolder { +class While : Loop(), BranchingNode, ArgumentHolder, DeclarationHolder, HasLocals { @Relationship(value = "CONDITION_DECLARATION") var conditionDeclarationEdge = astOptionalEdgeOf() /** C++ allows defining a declaration instead of a pure logical expression as condition */ @@ -81,10 +88,12 @@ class While : Loop(), BranchingNode, ArgumentHolder { return super.equals(other) && conditionDeclaration == other.conditionDeclaration && - condition == other.condition + condition == other.condition && + propertyEqualsList(localEdges, other.localEdges) } - override fun hashCode() = Objects.hash(super.hashCode(), conditionDeclaration, condition) + override fun hashCode() = + Objects.hash(super.hashCode(), conditionDeclaration, condition, locals) override fun getStartingPrevEOG(): Collection { val astChildren = this.allChildren { true } @@ -96,4 +105,21 @@ class While : Loop(), BranchingNode, ArgumentHolder { override fun getExitNextEOG(): Collection { return this.nextEOG.filter { it !in statement.allChildren { true } } } + + @Relationship(value = "LOCALS", direction = Relationship.Direction.OUTGOING) + override var localEdges = astEdgesOf() + + /** Virtual property to access [localEdges] without property edges. */ + override var locals by unwrapping(While::localEdges) + + override fun addDeclaration(declaration: Declaration) { + if (declaration is Variable) { + addIfNotContains(localEdges, declaration) + } else if (declaration is Function) { + addIfNotContains(localEdges, declaration) + } + } + + override val declarations: List + get() = locals } From 6becfb3301794f0f24ffde165034cec87db0aea3 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 12 Jun 2026 19:53:43 +0200 Subject: [PATCH 02/13] Fix --- .../aisec/cpg/graph/DeclarationHolder.kt | 92 +++++++++---------- .../aisec/cpg/graph/ExpressionBuilder.kt | 1 - .../graph/declarations/DeclarationSequence.kt | 1 + .../aisec/cpg/graph/declarations/Extension.kt | 1 + .../graph/declarations/FunctionTemplate.kt | 1 + .../cpg/graph/declarations/RecordTemplate.kt | 1 + .../graph/expressions/ArrayConstruction.kt | 1 + .../aisec/cpg/graph/expressions/Assign.kt | 13 ++- .../aisec/cpg/graph/expressions/Block.kt | 1 + .../cpg/graph/expressions/Comprehension.kt | 3 +- .../graph/expressions/DeclarationStatement.kt | 1 + .../aisec/cpg/graph/expressions/DoWhile.kt | 1 + .../aisec/cpg/graph/expressions/Switch.kt | 1 + .../aisec/cpg/graph/expressions/Try.kt | 1 + .../aisec/cpg/graph/expressions/While.kt | 1 + 15 files changed, 71 insertions(+), 49 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationHolder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationHolder.kt index 8e400e71970..d4f6c8de61b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationHolder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/DeclarationHolder.kt @@ -31,63 +31,63 @@ import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdge import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdges import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeList -interface DeclarationHolder { - /** - * Adds the specified declaration to this declaration holder. Ideally, the declaration holder - * should use the [addIfNotContains] method to consistently add declarations. - * - * @param declaration the declaration - */ - fun addDeclaration(declaration: Declaration) +fun addIfNotContains(collection: MutableCollection, declaration: T) { + if (!collection.contains(declaration)) { + collection.add(declaration) + } +} + +fun > addIfNotContains(collection: AstEdges, declaration: T) { + addIfNotContains(collection, declaration, true) +} + +fun > addIfNotContains(collection: EdgeList, declaration: T) { + addIfNotContains(collection, declaration, true) +} - fun addIfNotContains(collection: MutableCollection, declaration: T) { - if (!collection.contains(declaration)) { - collection.add(declaration) +/** + * Adds a declaration to a collection of property edges, which contain the declarations + * + * @param collection the collection + * @param declaration the declaration + * @param the type of the declaration + * @param outgoing whether the property is outgoing + */ +fun > addIfNotContains( + collection: EdgeList, + declaration: T, + outgoing: Boolean, +) { + var contains = false + for (element in collection) { + if (outgoing) { + if (element.end == declaration) { + contains = true + break + } + } else { + if (element.start == declaration) { + contains = true + break + } } } - fun > addIfNotContains(collection: AstEdges, declaration: T) { - addIfNotContains(collection, declaration, true) + if (contains) { + return } - fun > addIfNotContains(collection: EdgeList, declaration: T) { - addIfNotContains(collection, declaration, true) - } + collection.add(declaration) +} +interface DeclarationHolder { /** - * Adds a declaration to a collection of property edges, which contain the declarations + * Adds the specified declaration to this declaration holder. Ideally, the declaration holder + * should use the [addIfNotContains] method to consistently add declarations. * - * @param collection the collection * @param declaration the declaration - * @param the type of the declaration - * @param outgoing whether the property is outgoing */ - fun > addIfNotContains( - collection: EdgeList, - declaration: T, - outgoing: Boolean, - ) { - var contains = false - for (element in collection) { - if (outgoing) { - if (element.end == declaration) { - contains = true - break - } - } else { - if (element.start == declaration) { - contains = true - break - } - } - } - - if (contains) { - return - } - - collection.add(declaration) - } + fun addDeclaration(declaration: Declaration) val declarations: List } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt index f17cb5e501b..262ce362498 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt @@ -645,7 +645,6 @@ fun Literal.duplicate(implicit: Boolean): Literal { duplicate.assignedTypes = this.assignedTypes duplicate.code = this.code duplicate.location = this.location - duplicate.locals = this.locals duplicate.argumentIndex = this.argumentIndex duplicate.annotations = this.annotations duplicate.comment = this.comment diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/DeclarationSequence.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/DeclarationSequence.kt index 62f86daefc7..c7f944c40b3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/DeclarationSequence.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/DeclarationSequence.kt @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.DeclarationHolder +import de.fraunhofer.aisec.cpg.graph.addIfNotContains /** * This represents a sequence of one or more declaration(s). The purpose of this node is primarily diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Extension.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Extension.kt index 8a58ef22fcb..87d72c5904f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Extension.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Extension.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.graph.declarations import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import java.util.Objects diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionTemplate.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionTemplate.kt index 7c9c6c164fd..4beed50ce05 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionTemplate.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/FunctionTemplate.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordTemplate.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordTemplate.kt index 962269c5279..d971d0c59fc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordTemplate.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/RecordTemplate.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.declarations +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ArrayConstruction.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ArrayConstruction.kt index 3133d1f2c06..6970a144deb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ArrayConstruction.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/ArrayConstruction.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.HasInitializer import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.declarations.Variable import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Assign.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Assign.kt index 1fea031e10e..9384e250db8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Assign.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Assign.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.Variable import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping @@ -56,7 +57,12 @@ import org.slf4j.LoggerFactory * from the (first) rhs to the [Assign] itself. */ class Assign : - Expression(false), AssignmentHolder, ArgumentHolder, HasType.TypeObserver, HasOperatorCode { + Expression(false), + AssignmentHolder, + ArgumentHolder, + HasType.TypeObserver, + DeclarationHolder, + HasOperatorCode { override var operatorCode: String = "=" @@ -154,6 +160,11 @@ class Assign : } @Relationship("DECLARATIONS") var declarationEdges = astEdgesOf() + + override fun addDeclaration(declaration: Declaration) { + (declaration as? Variable)?.let { this.declarations.add(it) } + } + /** * Some languages, such as Go explicitly allow the definition / declaration of variables in the * assignment (known as a "short assignment"). Some languages, such as Python even implicitly diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Block.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Block.kt index 9dae5fd6c63..dbc174fa76d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Block.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Block.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.StatementHolder +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.Function import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Comprehension.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Comprehension.kt index 3163654788c..7c7c0850221 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Comprehension.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Comprehension.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.ArgumentHolder import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.allChildren import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.Function @@ -50,7 +51,7 @@ class Comprehension : Expression(), ArgumentHolder, DeclarationHolder, HasLocals var variableEdge = astEdgeOf( of = ProblemExpression("Missing variableEdge in ${this::class}"), - onChanged = { _, new -> (new?.end as? Expression)?.access = AccessValues.WRITE }, + onChanged = { _, new -> new?.end?.access = AccessValues.WRITE }, ) /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DeclarationStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DeclarationStatement.kt index 9b736401e0e..22f1a364cfb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DeclarationStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DeclarationStatement.kt @@ -27,6 +27,7 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.edges.Edge.Companion.propertyEqualsList import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DoWhile.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DoWhile.kt index 31572dad428..a965ba58221 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DoWhile.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/DoWhile.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.ArgumentHolder import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.allChildren import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.Function diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Switch.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Switch.kt index ba77890ed7b..eb491eff3e2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Switch.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Switch.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.Function import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Try.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Try.kt index 9c71fee9590..e7b9372531e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Try.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Try.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.Function import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/While.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/While.kt index ab81c63f4e2..79f883dc635 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/While.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/While.kt @@ -31,6 +31,7 @@ import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.HasLocals import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.addIfNotContains import de.fraunhofer.aisec.cpg.graph.allChildren import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.Function From 6418d937db2e07afa454f253c740dee97dada055 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 12 Jun 2026 19:54:35 +0200 Subject: [PATCH 03/13] Optimized EdgeList and EdgeSet implementation for single-item edges --- .../cpg/evaluation/MultiValueEvaluator.kt | 8 +- .../cpg/graph/edges/collections/EdgeList.kt | 195 ++++++++++++++---- .../cpg/graph/edges/collections/EdgeSet.kt | 172 +++++++++++++-- .../graph/edges/collections/EdgeListTest.kt | 29 +++ .../edges/collections/UnwrappedEdgeSetTest.kt | 32 +++ 5 files changed, 367 insertions(+), 69 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/evaluation/MultiValueEvaluator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/evaluation/MultiValueEvaluator.kt index d33354d9d4c..b2eb8eea5b3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/evaluation/MultiValueEvaluator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/evaluation/MultiValueEvaluator.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.evaluation +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.Field import de.fraunhofer.aisec.cpg.graph.declarations.Variable @@ -297,8 +298,11 @@ class MultiValueEvaluator : ValueEvaluator() { if (loop == null || loop.condition !is BinaryOperator) return setOf() var loopVar: Any? = - evaluateInternal(loop.initializerStatement?.declarations?.first(), depth) as? Number - ?: return setOf() + evaluateInternal( + (loop.initializerStatement as? DeclarationHolder)?.declarations?.first(), + depth, + ) + as? Number ?: return setOf() val cond = loop.condition as BinaryOperator val result = mutableSetOf() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt index 48793af7f4c..a765fb5977d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.graph.edges.collections import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.edges.Edge import java.util.function.Predicate +import kotlin.collections.AbstractMutableList /** This class extends a list of edges. This allows us to use lists of edges more conveniently. */ abstract class EdgeList>( @@ -44,50 +45,165 @@ abstract class EdgeList>( * operations to a larger list. */ initialCapacity: Int = 1, -) : ArrayList(initialCapacity), EdgeCollection { +) : AbstractMutableList(), EdgeCollection { + + // Draft: compact 0/1/many storage to avoid allocating an ArrayList per node in the common case. + private var first: EdgeType? = null + private var many: MutableList? = null + private var cachedCapacity: Int = initialCapacity + + override val size: Int + get() = many?.size ?: if (first == null) 0 else 1 + + override fun get(index: Int): EdgeType { + return when { + many != null -> many!![index] + index == 0 && first != null -> first!! + else -> throw IndexOutOfBoundsException("Index: $index, Size: $size") + } + } override fun add(element: EdgeType): Boolean { - // Make sure, the index is always set if (element.index == null) { element.index = this.size } - val ok = super.add(element) - if (ok) { - handleOnAdd(element) + when { + many != null -> many!!.add(element) + first == null -> first = element + else -> { + many = + ArrayList(maxOf(cachedCapacity, 2)).also { + it.add(first!!) + it.add(element) + } + first = null + } + } + + handleOnAdd(element) + return true + } + + override fun add(index: Int, element: EdgeType) { + if (index < 0 || index > size) { + throw IndexOutOfBoundsException("Index: $index, Size: $size") + } + + when { + many != null -> many!!.add(index, element) + first == null -> { + if (index != 0) throw IndexOutOfBoundsException("Index: $index, Size: $size") + first = element + } + else -> { + val prev = first!! + many = + ArrayList(maxOf(cachedCapacity, 2)).also { + if (index == 0) { + it.add(element) + it.add(prev) + } else { + it.add(prev) + it.add(element) + } + } + first = null + } + } + + handleOnAdd(element) + updateIndicesFrom(index) + } + + override fun removeAt(index: Int): EdgeType { + if (index < 0 || index >= size) { + throw IndexOutOfBoundsException("Index: $index, Size: $size") + } + + val removed = + when { + many != null -> { + val r = many!!.removeAt(index) + if (many!!.size == 1) { + first = many!![0] + many = null + } + r + } + else -> { + val r = first!! + first = null + r + } + } + + handleOnRemove(removed) + updateIndicesFrom(index) + return removed + } + + override fun set(index: Int, element: EdgeType): EdgeType { + if (index < 0 || index >= size) { + throw IndexOutOfBoundsException("Index: $index, Size: $size") + } + + return when { + many != null -> { + val prev = many!![index] + many!![index] = element + prev + } + else -> { + val prev = first!! + first = element + prev + } } - return ok } override fun remove(element: EdgeType): Boolean { - val ok = super.remove(element) - if (ok) { - handleOnRemove(element) + val idx = indexOf(element) + if (idx == -1) { + return false } - return ok + + removeAt(idx) + return true } override fun removeAll(elements: Collection): Boolean { - val ok = super.removeAll(elements.toSet()) - if (ok) { - elements.forEach { handleOnRemove(it) } + if (elements.isEmpty() || isEmpty()) { + return false + } + + val toRemove = elements.toSet() + var changed = false + + for (i in size - 1 downTo 0) { + if (this[i] in toRemove) { + removeAt(i) + changed = true + } } - return ok + + return changed } - override fun removeAt(index: Int): EdgeType { - val edge = super.removeAt(index) - handleOnRemove(edge) - return edge + override fun clear() { + if (isEmpty()) { + return + } + + val edges = this.toList() + first = null + many = null + edges.forEach { handleOnRemove(it) } } override fun removeIf(predicate: Predicate): Boolean { - val edges = filter { predicate.test(it) } - val ok = super.removeIf(predicate) - if (ok) { - edges.forEach { handleOnRemove(it) } - } - return ok + val toRemove = this.filter { predicate.test(it) } + return removeAll(toRemove) } /** Replaces the first occurrence of an edge with [old] with a new edge to [new]. */ @@ -101,13 +217,6 @@ abstract class EdgeList>( return false } - override fun clear() { - // Make a copy of our edges so we can pass a copy to our on-remove handler - val edges = this.toList() - super.clear() - edges.forEach { handleOnRemove(it) } - } - /** * This function creates a new edge (of [EdgeType]) to/from the specified node [target] * (depending on [outgoing]) and adds it to the specified index in the list. @@ -118,20 +227,6 @@ abstract class EdgeList>( return add(index, edge) } - override fun add(index: Int, element: EdgeType) { - // Make sure, the index is always set - element.index = this.size - - super.add(index, element) - - handleOnAdd(element) - - // We need to re-compute all edges with an index > inserted index - for (i in index until this.size) { - this[i].index = i - } - } - override fun toNodeCollection(predicate: ((EdgeType) -> Boolean)?): List { return internalToNodeCollection(this, outgoing, predicate, ::ArrayList) } @@ -163,4 +258,14 @@ abstract class EdgeList>( override fun hashCode(): Int { return internalHashcode(this, outgoing) } + + private fun updateIndicesFrom(startIndex: Int) { + if (startIndex < 0) { + return + } + + for (i in startIndex until size) { + this[i].index = i + } + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt index 1beeff90543..f016b5b0a0a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.graph.edges.collections import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.edges.Edge import java.util.function.Predicate +import kotlin.collections.AbstractMutableSet /** * This class extends a list of property edges. This allows us to use list of property edges more @@ -42,44 +43,109 @@ abstract class EdgeSet>( // We explicitly set the capacity to 1, as we expect that most nodes will only have one edge of // a given type. This is a common case for many edge types in the CPG, and setting the initial // capacity to 1 can save memory in these cases. -) : HashSet(1), EdgeCollection { +) : AbstractMutableSet(), EdgeCollection { + + // Draft: compact 0/1/many storage to avoid eagerly allocating a HashSet for singleton cases. + private var first: EdgeType? = null + private var many: MutableSet? = null + + override val size: Int + get() = many?.size ?: if (first == null) 0 else 1 + override fun add(element: EdgeType): Boolean { - val ok = super.add(element) - if (ok) { - handleOnAdd(element) + return when { + many != null -> { + val ok = many!!.add(element) + if (ok) { + handleOnAdd(element) + } + ok + } + first == null -> { + first = element + handleOnAdd(element) + true + } + first == element -> false + else -> { + many = + HashSet(2).also { + it.add(first!!) + it.add(element) + } + first = null + handleOnAdd(element) + true + } } - return ok } - override fun removeIf(predicate: Predicate): Boolean { - val edges = filter { predicate.test(it) } - val ok = super.removeIf(predicate) - if (ok) { - edges.forEach { handleOnRemove(it) } + override fun iterator(): MutableIterator { + val singleton = first + return if (many != null) { + ManyIterator(many!!.iterator()) + } else { + SingletonIterator(singleton) } - return ok } - override fun removeAll(elements: Collection): Boolean { - val ok = super.removeAll(elements.toSet()) - if (ok) { - elements.forEach { handleOnRemove(it) } - } - return ok + override fun contains(element: EdgeType): Boolean { + return many?.contains(element) ?: (first == element) } override fun remove(element: EdgeType): Boolean { - val ok = super.remove(element) - if (ok) { - handleOnRemove(element) + return when { + many != null -> { + val ok = many!!.remove(element) + if (ok) { + handleOnRemove(element) + compactManyIfPossible() + } + ok + } + first == element -> { + first = null + handleOnRemove(element) + true + } + else -> false + } + } + + override fun removeIf(predicate: Predicate): Boolean { + val toRemove = this.filter { predicate.test(it) } + return removeAll(toRemove) + } + + override fun removeAll(elements: Collection): Boolean { + if (elements.isEmpty() || isEmpty()) { + return false + } + + var changed = false + val toRemove = elements.toSet() + val it = iterator() + + while (it.hasNext()) { + val edge = it.next() + if (edge in toRemove) { + it.remove() + changed = true + } } - return ok + + return changed } override fun clear() { + if (isEmpty()) { + return + } + // Make a copy of our edges so we can pass a copy to our on-remove handler val edges = this.toSet() - super.clear() + first = null + many = null edges.forEach { handleOnRemove(it) } } @@ -106,4 +172,66 @@ abstract class EdgeSet>( override fun hashCode(): Int { return internalHashcode(this, outgoing) } + + private fun compactManyIfPossible() { + val current = many ?: return + when (current.size) { + 0 -> many = null + 1 -> { + first = current.first() + many = null + } + } + } + + private inner class ManyIterator(private val delegate: MutableIterator) : + MutableIterator { + private var last: EdgeType? = null + + override fun hasNext(): Boolean { + return delegate.hasNext() + } + + override fun next(): EdgeType { + return delegate.next().also { last = it } + } + + override fun remove() { + val removed = + last ?: throw IllegalStateException("next() must be called before remove()") + delegate.remove() + handleOnRemove(removed) + compactManyIfPossible() + last = null + } + } + + private inner class SingletonIterator(private val edge: EdgeType?) : MutableIterator { + private var consumed = false + private var canRemove = false + + override fun hasNext(): Boolean { + return !consumed && edge != null + } + + override fun next(): EdgeType { + if (!hasNext()) { + throw NoSuchElementException() + } + + consumed = true + canRemove = true + return edge!! + } + + override fun remove() { + if (!canRemove || edge == null) { + throw IllegalStateException("next() must be called before remove()") + } + + first = null + handleOnRemove(edge) + canRemove = false + } + } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeListTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeListTest.kt index ad58d2ada09..5e119f83d23 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeListTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeListTest.kt @@ -67,4 +67,33 @@ class EdgeListTest { assertEquals>(listOf(node2, node4, node3), unwrapped) } } + + @Test + fun testCompactStorageTransitions() { + with(TestLanguageFrontend()) { + val owner = newLiteral(0) + val n1 = newLiteral(1) + val n2 = newLiteral(2) + val n3 = newLiteral(3) + + val list = AstEdges>(thisRef = owner) + assertEquals(0, list.size) + + list += n1 + assertEquals>(listOf(n1), list.unwrap()) + + list += n2 + assertEquals>(listOf(n1, n2), list.unwrap()) + + list.removeAt(0) + assertEquals>(listOf(n2), list.unwrap()) + + list += n3 + assertEquals>(listOf(n2, n3), list.unwrap()) + + list.clear() + assertEquals(0, list.size) + assertEquals(emptyList(), list.unwrap()) + } + } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/UnwrappedEdgeSetTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/UnwrappedEdgeSetTest.kt index ccba73cd08c..a92bab32554 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/UnwrappedEdgeSetTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/UnwrappedEdgeSetTest.kt @@ -49,4 +49,36 @@ class UnwrappedEdgeSetTest { assertEquals(dfgSet, nodeSet) } } + + @Test + fun testCompactStorageTransitions() { + with(TestLanguageFrontend()) { + val node1 = newLiteral(1) + val node2 = newLiteral(2) + val node3 = newLiteral(3) + + assertEquals(0, node1.nextDFGEdges.size) + assertEquals(0, node1.nextDFG.size) + + node1.nextDFG += node2 + assertEquals(1, node1.nextDFGEdges.size) + assertEquals(setOf(node2), node1.nextDFG.toSet()) + assertEquals(setOf(node1), node2.prevDFG.toSet()) + + node1.nextDFG += node3 + assertEquals(2, node1.nextDFGEdges.size) + assertEquals(setOf(node2, node3), node1.nextDFG.toSet()) + assertEquals(setOf(node1), node2.prevDFG.toSet()) + assertEquals(setOf(node1), node3.prevDFG.toSet()) + + node1.nextDFG.remove(node2) + assertEquals(1, node1.nextDFGEdges.size) + assertEquals(setOf(node3), node1.nextDFG.toSet()) + assertEquals(0, node2.prevDFG.size) + + node1.nextDFG.clear() + assertEquals(0, node1.nextDFGEdges.size) + assertEquals(0, node3.prevDFG.size) + } + } } From 9deb41238628bc81424c7fec833a2531cd5b6261 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 12 Jun 2026 19:59:43 +0200 Subject: [PATCH 04/13] Fix frontends --- .../aisec/cpg/graph/expressions/Lambda.kt | 13 ++++++++++++- .../aisec/cpg/frontends/llvm/StatementHandler.kt | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Lambda.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Lambda.kt index 2edb0266dda..4a2bd3bd56c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Lambda.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Lambda.kt @@ -26,8 +26,11 @@ package de.fraunhofer.aisec.cpg.graph.expressions import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.declarations.Function import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration +import de.fraunhofer.aisec.cpg.graph.declarations.Variable +import de.fraunhofer.aisec.cpg.graph.edges.ast.astEdgesOf import de.fraunhofer.aisec.cpg.graph.edges.ast.astOptionalEdgeOf import de.fraunhofer.aisec.cpg.graph.edges.unwrapping import de.fraunhofer.aisec.cpg.graph.types.FunctionType @@ -39,7 +42,15 @@ import de.fraunhofer.aisec.cpg.persistence.Relationship * This expression denotes the usage of an anonymous / lambda function. It connects the inner * anonymous function to the user of a lambda function with an expression. */ -class Lambda : Expression(), HasType.TypeObserver { +class Lambda : Expression(), DeclarationHolder, HasType.TypeObserver { + + @Relationship("DECLARATIONS") var declarationEdges = astEdgesOf() + + override fun addDeclaration(declaration: Declaration) { + (declaration as? Variable)?.let { this.declarations.add(it) } + } + + override var declarations by unwrapping(Lambda::declarationEdges) /** * If [areVariablesMutable] is false, only the (outer) variables in this list can be modified diff --git a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt index 6305ff1ece8..78ecfaed826 100644 --- a/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt +++ b/cpg-language-llvm/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandler.kt @@ -1206,7 +1206,7 @@ class StatementHandler(lang: LLVMIRLanguageFrontend) : val newArrayDecl = declarationOrNot(frontend.getOperandValueAtIndex(instr, 0), instr) compoundStatement.statements += newArrayDecl - val decl = newArrayDecl.declarations[0] as? Variable + val decl = (newArrayDecl as? DeclarationHolder)?.declarations?.firstOrNull() as? Variable val arrayExpr = newSubscription(rawNode = instr) arrayExpr.arrayExpression = newReference( From 3297523ae303a5ffd64bc5d1682f96fbcc6b57eb Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sat, 13 Jun 2026 08:03:39 +0200 Subject: [PATCH 05/13] Extract duplicate code --- .../edges/collections/CompactEdgeStorage.kt | 73 +++++++++++++++++++ .../cpg/graph/edges/collections/EdgeList.kt | 73 ++++++++----------- .../cpg/graph/edges/collections/EdgeSet.kt | 53 +++++--------- 3 files changed, 122 insertions(+), 77 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/CompactEdgeStorage.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/CompactEdgeStorage.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/CompactEdgeStorage.kt new file mode 100644 index 00000000000..30bc55e8d02 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/CompactEdgeStorage.kt @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2026, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.graph.edges.collections + +/** + * Shared helper for compact 0/1/many edge storage. + * + * In the common singleton case we avoid allocating a full list/set object and only materialize a + * collection when we transition to "many" elements. + */ +internal class CompactEdgeStorage>( + private val createMany: (capacity: Int) -> ManyType +) { + var first: EdgeType? = null + var many: ManyType? = null + + val size: Int + get() = many?.size ?: if (first == null) 0 else 1 + + fun ensureMany(minCapacity: Int = 2): ManyType { + val existing = many + if (existing != null) { + return existing + } + + val created = createMany(minCapacity) + first?.let { created.add(it) } + first = null + many = created + return created + } + + fun clearAndSnapshot(): List { + val snapshot = many?.toList() ?: first?.let { listOf(it) } ?: emptyList() + first = null + many = null + return snapshot + } + + fun compactManyToSingleton(singleExtractor: (ManyType) -> EdgeType?) { + val current = many ?: return + when (current.size) { + 0 -> many = null + 1 -> { + first = singleExtractor(current) + many = null + } + } + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt index a765fb5977d..3fba46de078 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt @@ -48,17 +48,17 @@ abstract class EdgeList>( ) : AbstractMutableList(), EdgeCollection { // Draft: compact 0/1/many storage to avoid allocating an ArrayList per node in the common case. - private var first: EdgeType? = null - private var many: MutableList? = null + private val storage = + CompactEdgeStorage> { cap -> ArrayList(cap) } private var cachedCapacity: Int = initialCapacity override val size: Int - get() = many?.size ?: if (first == null) 0 else 1 + get() = storage.size override fun get(index: Int): EdgeType { return when { - many != null -> many!![index] - index == 0 && first != null -> first!! + storage.many != null -> storage.many!![index] + index == 0 && storage.first != null -> storage.first!! else -> throw IndexOutOfBoundsException("Index: $index, Size: $size") } } @@ -69,15 +69,10 @@ abstract class EdgeList>( } when { - many != null -> many!!.add(element) - first == null -> first = element + storage.many != null -> storage.many!!.add(element) + storage.first == null -> storage.first = element else -> { - many = - ArrayList(maxOf(cachedCapacity, 2)).also { - it.add(first!!) - it.add(element) - } - first = null + storage.ensureMany(maxOf(cachedCapacity, 2)).add(element) } } @@ -91,24 +86,18 @@ abstract class EdgeList>( } when { - many != null -> many!!.add(index, element) - first == null -> { + storage.many != null -> storage.many!!.add(index, element) + storage.first == null -> { if (index != 0) throw IndexOutOfBoundsException("Index: $index, Size: $size") - first = element + storage.first = element } else -> { - val prev = first!! - many = - ArrayList(maxOf(cachedCapacity, 2)).also { - if (index == 0) { - it.add(element) - it.add(prev) - } else { - it.add(prev) - it.add(element) - } - } - first = null + val created = storage.ensureMany(maxOf(cachedCapacity, 2)) + if (index == 0) { + created.add(0, element) + } else { + created.add(element) + } } } @@ -123,17 +112,17 @@ abstract class EdgeList>( val removed = when { - many != null -> { - val r = many!!.removeAt(index) - if (many!!.size == 1) { - first = many!![0] - many = null + storage.many != null -> { + val r = storage.many!!.removeAt(index) + if (storage.many!!.size == 1) { + storage.first = storage.many!![0] + storage.many = null } r } else -> { - val r = first!! - first = null + val r = storage.first!! + storage.first = null r } } @@ -149,14 +138,14 @@ abstract class EdgeList>( } return when { - many != null -> { - val prev = many!![index] - many!![index] = element + storage.many != null -> { + val prev = storage.many!![index] + storage.many!![index] = element prev } else -> { - val prev = first!! - first = element + val prev = storage.first!! + storage.first = element prev } } @@ -195,9 +184,7 @@ abstract class EdgeList>( return } - val edges = this.toList() - first = null - many = null + val edges = storage.clearAndSnapshot() edges.forEach { handleOnRemove(it) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt index f016b5b0a0a..4c1e3cd3913 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt @@ -46,34 +46,28 @@ abstract class EdgeSet>( ) : AbstractMutableSet(), EdgeCollection { // Draft: compact 0/1/many storage to avoid eagerly allocating a HashSet for singleton cases. - private var first: EdgeType? = null - private var many: MutableSet? = null + private val storage = CompactEdgeStorage> { cap -> HashSet(cap) } override val size: Int - get() = many?.size ?: if (first == null) 0 else 1 + get() = storage.size override fun add(element: EdgeType): Boolean { return when { - many != null -> { - val ok = many!!.add(element) + storage.many != null -> { + val ok = storage.many!!.add(element) if (ok) { handleOnAdd(element) } ok } - first == null -> { - first = element + storage.first == null -> { + storage.first = element handleOnAdd(element) true } - first == element -> false + storage.first == element -> false else -> { - many = - HashSet(2).also { - it.add(first!!) - it.add(element) - } - first = null + storage.ensureMany(2).add(element) handleOnAdd(element) true } @@ -81,30 +75,30 @@ abstract class EdgeSet>( } override fun iterator(): MutableIterator { - val singleton = first - return if (many != null) { - ManyIterator(many!!.iterator()) + val singleton = storage.first + return if (storage.many != null) { + ManyIterator(storage.many!!.iterator()) } else { SingletonIterator(singleton) } } override fun contains(element: EdgeType): Boolean { - return many?.contains(element) ?: (first == element) + return storage.many?.contains(element) ?: (storage.first == element) } override fun remove(element: EdgeType): Boolean { return when { - many != null -> { - val ok = many!!.remove(element) + storage.many != null -> { + val ok = storage.many!!.remove(element) if (ok) { handleOnRemove(element) compactManyIfPossible() } ok } - first == element -> { - first = null + storage.first == element -> { + storage.first = null handleOnRemove(element) true } @@ -143,9 +137,7 @@ abstract class EdgeSet>( } // Make a copy of our edges so we can pass a copy to our on-remove handler - val edges = this.toSet() - first = null - many = null + val edges = storage.clearAndSnapshot() edges.forEach { handleOnRemove(it) } } @@ -174,14 +166,7 @@ abstract class EdgeSet>( } private fun compactManyIfPossible() { - val current = many ?: return - when (current.size) { - 0 -> many = null - 1 -> { - first = current.first() - many = null - } - } + storage.compactManyToSingleton { it.firstOrNull() } } private inner class ManyIterator(private val delegate: MutableIterator) : @@ -229,7 +214,7 @@ abstract class EdgeSet>( throw IllegalStateException("next() must be called before remove()") } - first = null + storage.first = null handleOnRemove(edge) canRemove = false } From c3bf414568b6f9146a7becc29a66fadb5fc349e9 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sat, 13 Jun 2026 08:41:18 +0200 Subject: [PATCH 06/13] Changed mirroring --- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 28 ++++--- .../fraunhofer/aisec/cpg/graph/OverlayNode.kt | 2 +- .../cpg/graph/declarations/Declaration.kt | 10 ++- .../aisec/cpg/graph/declarations/Function.kt | 2 +- .../aisec/cpg/graph/edges/MemoryAddress.kt | 7 +- .../cpg/graph/edges/collections/EdgeList.kt | 53 ++++++++++++-- .../cpg/graph/edges/collections/EdgeSet.kt | 73 ++++++++++++++++--- .../edges/collections/EdgeSingletonList.kt | 31 +++++++- .../collections/MirroredEdgeCollection.kt | 53 ++++++++------ .../graph/edges/flows/ControlDependence.kt | 7 +- .../aisec/cpg/graph/edges/flows/Dataflow.kt | 3 +- .../cpg/graph/edges/flows/EvaluationOrder.kt | 3 +- .../aisec/cpg/graph/edges/flows/Invoke.kt | 3 +- .../graph/edges/flows/ProgramDependence.kt | 7 +- .../cpg/graph/edges/overlay/BasicBlockEdge.kt | 3 +- .../aisec/cpg/graph/edges/overlay/Overlay.kt | 9 +-- .../aisec/cpg/graph/edges/scopes/Import.kt | 3 +- .../aisec/cpg/graph/expressions/Call.kt | 6 +- .../aisec/cpg/graph/expressions/Expression.kt | 13 +++- .../cpg/graph/expressions/MemoryAddress.kt | 20 +++-- .../aisec/cpg/graph/overlays/BasicBlock.kt | 2 +- .../aisec/cpg/graph/scopes/NamespaceScope.kt | 2 +- .../aisec/cpg/graph/scopes/Scope.kt | 6 +- 23 files changed, 252 insertions(+), 94 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 69998f28ae5..c7062178f69 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -111,21 +111,25 @@ abstract class Node() : @Relationship(value = "EOG", direction = Relationship.Direction.INCOMING) @PopulatedByPass(EvaluationOrderGraphPass::class) var prevEOGEdges: EvaluationOrders = - EvaluationOrders(this, mirrorProperty = Node::nextEOGEdges, outgoing = false) + EvaluationOrders(this, mirroredCollection = { it.nextEOGEdges }, outgoing = false) protected set /** Outgoing control flow edges. */ @Relationship(value = "EOG", direction = Relationship.Direction.OUTGOING) @PopulatedByPass(EvaluationOrderGraphPass::class) var nextEOGEdges: EvaluationOrders = - EvaluationOrders(this, mirrorProperty = Node::prevEOGEdges, outgoing = true) + EvaluationOrders(this, mirroredCollection = { it.prevEOGEdges }, outgoing = true) protected set /** The [BasicBlockEdgeList] leading to the basic block this node belongs to. */ @Relationship(value = "BB", direction = Relationship.Direction.OUTGOING) @PopulatedByPass(BasicBlockCollectorPass::class) var basicBlockEdges: BasicBlockEdgeList = - BasicBlockEdgeList(this, mirrorProperty = BasicBlock::nodeEdges, outgoing = true) + BasicBlockEdgeList( + this, + mirroredCollection = { (it as BasicBlock).nodeEdges }, + outgoing = true, + ) protected set /** The basic block this node belongs to. */ @@ -138,7 +142,7 @@ abstract class Node() : @PopulatedByPass(ControlDependenceGraphPass::class) @Relationship(value = "CDG", direction = Relationship.Direction.OUTGOING) var nextCDGEdges: ControlDependences = - ControlDependences(this, mirrorProperty = Node::prevCDGEdges, outgoing = true) + ControlDependences(this, mirroredCollection = { it.prevCDGEdges }, outgoing = true) protected set var nextCDG by unwrapping(Node::nextCDGEdges) @@ -150,7 +154,7 @@ abstract class Node() : @PopulatedByPass(ControlDependenceGraphPass::class) @Relationship(value = "CDG", direction = Relationship.Direction.INCOMING) var prevCDGEdges: ControlDependences = - ControlDependences(this, mirrorProperty = Node::nextCDGEdges, outgoing = false) + ControlDependences(this, mirroredCollection = { it.nextCDGEdges }, outgoing = false) protected set var prevCDG by unwrapping(Node::prevCDGEdges) @@ -167,7 +171,7 @@ abstract class Node() : @Relationship(value = "DFG", direction = Relationship.Direction.INCOMING) @PopulatedByPass(DFGPass::class, PointsToPass::class) var prevDFGEdges: Dataflows = - Dataflows(this, mirrorProperty = Node::nextDFGEdges, outgoing = false) + Dataflows(this, mirroredCollection = { it.nextDFGEdges }, outgoing = false) protected set /** Virtual property for accessing [prevDFGEdges] without property edges. */ @@ -200,7 +204,7 @@ abstract class Node() : @PopulatedByPass(DFGPass::class, PointsToPass::class) @Relationship(value = "DFG", direction = Relationship.Direction.OUTGOING) var nextDFGEdges: Dataflows = - Dataflows(this, mirrorProperty = Node::prevDFGEdges, outgoing = true) + Dataflows(this, mirroredCollection = { it.prevDFGEdges }, outgoing = true) protected set /** Virtual property for accessing [nextDFGEdges] without property edges. */ @@ -233,7 +237,7 @@ abstract class Node() : @PopulatedByPass(ProgramDependenceGraphPass::class) @Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING) var nextPDGEdges: ProgramDependences = - ProgramDependences(this, mirrorProperty = Node::prevPDGEdges, outgoing = true) + ProgramDependences(this, mirroredCollection = { it.prevPDGEdges }, outgoing = true) protected set var nextPDG by unwrapping(Node::nextPDGEdges) @@ -242,7 +246,7 @@ abstract class Node() : @PopulatedByPass(ProgramDependenceGraphPass::class) @Relationship(value = "PDG", direction = Relationship.Direction.INCOMING) var prevPDGEdges: ProgramDependences = - ProgramDependences(this, mirrorProperty = Node::nextPDGEdges, outgoing = false) + ProgramDependences(this, mirroredCollection = { it.nextPDGEdges }, outgoing = false) protected set var prevPDG by unwrapping(Node::prevPDGEdges) @@ -292,7 +296,11 @@ abstract class Node() : @Relationship(value = "OVERLAY", direction = Relationship.Direction.OUTGOING) val overlayEdges: Overlays = - Overlays(this, mirrorProperty = OverlayNode::underlyingNodeEdge, outgoing = true) + Overlays( + this, + mirroredCollection = { (it as OverlayNode).underlyingNodeEdge }, + outgoing = true, + ) var overlays by unwrapping(Node::overlayEdges) /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt index 9bb58d9d988..54f212b8b47 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt @@ -48,7 +48,7 @@ abstract class OverlayNode() : Node() { OverlaySingleEdge( this, of = null, - mirrorProperty = Node::overlayEdges, + mirroredCollection = { it.overlayEdges }, outgoing = false, onChange = this::propagateNodePropertiesFromUnderlyingNode, ) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Declaration.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Declaration.kt index ab921a603c8..a341718f656 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Declaration.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Declaration.kt @@ -64,7 +64,7 @@ abstract class Declaration : AstNode(), HasModifiers, HasMemoryAddress, HasMemor @Relationship override var memoryAddressEdges: MemoryAddressEdges = memoryAddressEdgesOf( - mirrorProperty = MemoryAddress::usageEdges, + mirroredCollection = { (it as MemoryAddress).usageEdges }, outgoing = true, onAdd = { toAdd -> if (this.memoryAddressEdges.size > 1) { @@ -79,7 +79,11 @@ abstract class Declaration : AstNode(), HasModifiers, HasMemoryAddress, HasMemor /** Where the memory value of this declaration is used. */ @Relationship override var memoryValueUsageEdges = - Dataflows(this, mirrorProperty = HasMemoryValue::memoryValueEdges, outgoing = true) + Dataflows( + this, + mirroredCollection = { (it as HasMemoryValue).memoryValueEdges }, + outgoing = true, + ) override var memoryValueUsages by unwrapping(Declaration::memoryValueUsageEdges) /** Each Declaration can also have a MemoryValue. */ @@ -87,7 +91,7 @@ abstract class Declaration : AstNode(), HasModifiers, HasMemoryAddress, HasMemor override var memoryValueEdges = Dataflows( this, - mirrorProperty = HasMemoryValue::memoryValueUsageEdges, + mirroredCollection = { (it as HasMemoryValue).memoryValueUsageEdges }, outgoing = false, ) override var memoryValues by unwrapping(Declaration::memoryValueEdges) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Function.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Function.kt index b4cab735d17..97a4a8024a0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Function.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/declarations/Function.kt @@ -85,7 +85,7 @@ open class Function : */ @Relationship(value = "INVOKES", direction = Relationship.Direction.INCOMING) val calledByEdges: Invokes = - Invokes(this, Call::invokeEdges, outgoing = false) + Invokes(this, mirroredCollection = { (it as Call).invokeEdges }, outgoing = false) /** Virtual property for accessing [calledByEdges] without property edges. */ val calledBy: MutableList by unwrappingIncoming(Function::calledByEdges) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/MemoryAddress.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/MemoryAddress.kt index 61738cc9603..ed3be541efc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/MemoryAddress.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/MemoryAddress.kt @@ -29,7 +29,6 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeSet import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection import de.fraunhofer.aisec.cpg.graph.expressions.MemoryAddress -import kotlin.reflect.KProperty /** This edge class defines that [end] is a (possible) memory address of [start]. */ open class MemoryAddressEdge(start: Node, end: MemoryAddress, var outgoing: Boolean) : @@ -50,7 +49,7 @@ open class MemoryAddressEdge(start: Node, end: MemoryAddress, var outgoing: Bool /** This class represents a container of [MemoryAddressEdge] property edges in a [thisRef]. */ class MemoryAddressEdges( thisRef: Node, - override var mirrorProperty: KProperty>, + override var mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean, onAdd: ((MemoryAddressEdge) -> Unit)? = null, ) : @@ -64,13 +63,13 @@ class MemoryAddressEdges( /** Creates an [Node] container starting from this node. */ fun Node.memoryAddressEdgesOf( - mirrorProperty: KProperty>, + mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean, onAdd: ((MemoryAddressEdge) -> Unit)? = null, ): MemoryAddressEdges { return MemoryAddressEdges( thisRef = this, - mirrorProperty = mirrorProperty, + mirroredCollection = mirroredCollection, outgoing = outgoing, onAdd = onAdd, ) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt index 3fba46de078..fcf98b304d5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt @@ -45,7 +45,10 @@ abstract class EdgeList>( * operations to a larger list. */ initialCapacity: Int = 1, -) : AbstractMutableList(), EdgeCollection { +) : + AbstractMutableList(), + EdgeCollection, + MirrorBacklinkCollection { // Draft: compact 0/1/many storage to avoid allocating an ArrayList per node in the common case. private val storage = @@ -64,6 +67,35 @@ abstract class EdgeList>( } override fun add(element: EdgeType): Boolean { + addWithoutHooks(element) + handleOnAdd(element) + return true + } + + override fun addMirrorBacklink(element: EdgeType): Boolean { + if (containsMirrorBacklinkByIdentity(element)) { + return false + } + + addWithoutHooks(element) + return true + } + + override fun containsMirrorBacklinkByIdentity(element: EdgeType): Boolean { + return indexOfIdentity(element) != -1 + } + + override fun removeMirrorBacklink(element: EdgeType): Boolean { + val idx = indexOfIdentity(element) + if (idx == -1) { + return false + } + + removeAtWithoutHooks(idx) + return true + } + + private fun addWithoutHooks(element: EdgeType) { if (element.index == null) { element.index = this.size } @@ -75,9 +107,6 @@ abstract class EdgeList>( storage.ensureMany(maxOf(cachedCapacity, 2)).add(element) } } - - handleOnAdd(element) - return true } override fun add(index: Int, element: EdgeType) { @@ -110,6 +139,13 @@ abstract class EdgeList>( throw IndexOutOfBoundsException("Index: $index, Size: $size") } + val removed = removeAtWithoutHooks(index) + + handleOnRemove(removed) + return removed + } + + private fun removeAtWithoutHooks(index: Int): EdgeType { val removed = when { storage.many != null -> { @@ -127,7 +163,6 @@ abstract class EdgeList>( } } - handleOnRemove(removed) updateIndicesFrom(index) return removed } @@ -246,6 +281,14 @@ abstract class EdgeList>( return internalHashcode(this, outgoing) } + private fun indexOfIdentity(element: EdgeType): Int { + return when { + storage.many != null -> storage.many!!.indexOfFirst { it === element } + storage.first === element -> 0 + else -> -1 + } + } + private fun updateIndicesFrom(startIndex: Int) { if (startIndex < 0) { return diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt index 4c1e3cd3913..17343ce060e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt @@ -43,7 +43,10 @@ abstract class EdgeSet>( // We explicitly set the capacity to 1, as we expect that most nodes will only have one edge of // a given type. This is a common case for many edge types in the CPG, and setting the initial // capacity to 1 can save memory in these cases. -) : AbstractMutableSet(), EdgeCollection { +) : + AbstractMutableSet(), + EdgeCollection, + MirrorBacklinkCollection { // Draft: compact 0/1/many storage to avoid eagerly allocating a HashSet for singleton cases. private val storage = CompactEdgeStorage> { cap -> HashSet(cap) } @@ -52,23 +55,40 @@ abstract class EdgeSet>( get() = storage.size override fun add(element: EdgeType): Boolean { + val ok = addWithoutHooks(element) + if (ok) { + handleOnAdd(element) + } + return ok + } + + override fun addMirrorBacklink(element: EdgeType): Boolean { + return addWithoutHooks(element) + } + + override fun containsMirrorBacklinkByIdentity(element: EdgeType): Boolean { + return when { + storage.many != null -> storage.many!!.any { it === element } + else -> storage.first === element + } + } + + override fun removeMirrorBacklink(element: EdgeType): Boolean { + return removeByIdentityWithoutHooks(element) + } + + private fun addWithoutHooks(element: EdgeType): Boolean { return when { storage.many != null -> { - val ok = storage.many!!.add(element) - if (ok) { - handleOnAdd(element) - } - ok + storage.many!!.add(element) } storage.first == null -> { storage.first = element - handleOnAdd(element) true } storage.first == element -> false else -> { storage.ensureMany(2).add(element) - handleOnAdd(element) true } } @@ -88,18 +108,51 @@ abstract class EdgeSet>( } override fun remove(element: EdgeType): Boolean { + val ok = removeByEqualityWithoutHooks(element) + if (ok) { + handleOnRemove(element) + } + return ok + } + + private fun removeByEqualityWithoutHooks(element: EdgeType): Boolean { return when { storage.many != null -> { val ok = storage.many!!.remove(element) if (ok) { - handleOnRemove(element) compactManyIfPossible() } ok } storage.first == element -> { storage.first = null - handleOnRemove(element) + true + } + else -> false + } + } + + private fun removeByIdentityWithoutHooks(element: EdgeType): Boolean { + return when { + storage.many != null -> { + val it = storage.many!!.iterator() + var removed = false + while (it.hasNext()) { + if (it.next() === element) { + it.remove() + removed = true + break + } + } + + if (removed) { + compactManyIfPossible() + } + + removed + } + storage.first === element -> { + storage.first = null true } else -> false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSingletonList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSingletonList.kt index 565e6c308f8..e1dd7aa2de3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSingletonList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSingletonList.kt @@ -44,7 +44,7 @@ open class EdgeSingletonList< var onChanged: ((old: EdgeType?, new: EdgeType?) -> Unit)? = null, override var outgoing: Boolean, of: NullableNodeType, -) : EdgeCollection { +) : EdgeCollection, MirrorBacklinkCollection { var element: EdgeType? = if (of == null) { @@ -84,6 +84,35 @@ open class EdgeSingletonList< } } + override fun addMirrorBacklink(element: EdgeType): Boolean { + if (this.element === element) { + return false + } + + if (this.element != null) { + return false + } + + this.element = element + onChanged?.invoke(null, this.element) + return true + } + + override fun removeMirrorBacklink(element: EdgeType): Boolean { + if (this.element !== element) { + return false + } + + val old = this.element + this.element = null + onChanged?.invoke(old, null) + return true + } + + override fun containsMirrorBacklinkByIdentity(element: EdgeType): Boolean { + return this.element === element + } + override fun addAll(elements: Collection): Boolean { throw UnsupportedOperationException() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt index e817aa7bab0..218eb40f0fa 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt @@ -27,7 +27,15 @@ package de.fraunhofer.aisec.cpg.graph.edges.collections import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.edges.Edge -import kotlin.reflect.KProperty + +/** Internal operations for mirroring without recursively triggering hooks. */ +internal interface MirrorBacklinkCollection { + fun addMirrorBacklink(element: ElementType): Boolean + + fun removeMirrorBacklink(element: ElementType): Boolean + + fun containsMirrorBacklinkByIdentity(element: ElementType): Boolean +} /** * This interface can be used for edge collections, which "mirror" its content to another property. @@ -35,20 +43,18 @@ import kotlin.reflect.KProperty */ interface MirroredEdgeCollection> : EdgeCollection { - var mirrorProperty: KProperty> + /** Provides direct access to the mirrored collection without reflection. */ + var mirroredCollection: (Node) -> MutableCollection override fun handleOnRemove(edge: PropertyEdgeType) { // Handle our mirror property. - if (outgoing) { - var prevOfNext = mirrorProperty.call(edge.end) - if (edge in prevOfNext) { - prevOfNext.remove(edge) - } - } else { - var nextOfPrev = mirrorProperty.call(edge.start) - if (edge in nextOfPrev) { - nextOfPrev.remove(edge) - } + val mirror = if (outgoing) mirroredCollection(edge.end) else mirroredCollection(edge.start) + + @Suppress("UNCHECKED_CAST") + if (mirror is MirrorBacklinkCollection<*>) { + (mirror as MirrorBacklinkCollection).removeMirrorBacklink(edge) + } else if (edge in mirror) { + mirror.remove(edge) } // Execute any remaining pre actions @@ -56,22 +62,21 @@ interface MirroredEdgeCollection) { + val backlink = mirror as MirrorBacklinkCollection + if (!backlink.containsMirrorBacklinkByIdentity(edge)) { + backlink.addMirrorBacklink(edge) } + } else if (edge !in mirror) { + mirror.add(edge) } // Execute any remaining post actions diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/ControlDependence.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/ControlDependence.kt index ed895219bda..30567d48708 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/ControlDependence.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/ControlDependence.kt @@ -29,7 +29,6 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeList import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass -import kotlin.reflect.KProperty /** * An edge in a Control Dependence Graph (CDG). Denotes that the [start] node exercises control @@ -66,13 +65,13 @@ class ControlDependence( class ControlDependences : EdgeList, MirroredEdgeCollection { - override var mirrorProperty: KProperty> + override var mirroredCollection: (Node) -> MutableCollection constructor( thisRef: Node, - mirrorProperty: KProperty>, + mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean, ) : super(thisRef = thisRef, init = ::ControlDependence, outgoing = outgoing) { - this.mirrorProperty = mirrorProperty + this.mirroredCollection = mirroredCollection } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Dataflow.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Dataflow.kt index fffc57b13c0..c7b9986d79d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Dataflow.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Dataflow.kt @@ -39,7 +39,6 @@ import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.persistence.Convert import de.fraunhofer.aisec.cpg.persistence.converters.DataflowGranularityConverter import java.util.Objects -import kotlin.reflect.KProperty /** * The granularity of the data-flow, e.g., whether the flow contains the whole object, or just a @@ -272,7 +271,7 @@ class ContextSensitiveDataflow( /** This class represents a container of [Dataflow] property edges in a [thisRef]. */ class Dataflows( thisRef: Node, - override var mirrorProperty: KProperty>, + override var mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean, ) : EdgeSet(thisRef = thisRef, init = ::Dataflow, outgoing = outgoing), diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/EvaluationOrder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/EvaluationOrder.kt index 4d99bafbc36..7ff66018080 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/EvaluationOrder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/EvaluationOrder.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.graph.edges.Edge import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeList import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass -import kotlin.reflect.KProperty /** * An edge in our Evaluation Order Graph (EOG). It considers the order in which our AST statements @@ -89,7 +88,7 @@ class EvaluationOrder( */ class EvaluationOrders( thisRef: Node, - override var mirrorProperty: KProperty>, + override var mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean = true, ) : EdgeList( diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Invoke.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Invoke.kt index 2dc8739a037..72d8ef9ec8d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Invoke.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Invoke.kt @@ -31,7 +31,6 @@ import de.fraunhofer.aisec.cpg.graph.edges.Edge import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeList import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection import de.fraunhofer.aisec.cpg.graph.expressions.Call -import kotlin.reflect.KProperty /** This edge class denotes the invocation of a [Function] by a [Call]. */ class Invoke( @@ -61,7 +60,7 @@ class Invoke( /** A container for [Usage] edges. [NodeType] is necessary for type safety. */ class Invokes( thisRef: Node, - override var mirrorProperty: KProperty>, + override var mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean = true, ) : EdgeList(thisRef = thisRef, init = ::Invoke, outgoing = outgoing), diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/ProgramDependence.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/ProgramDependence.kt index af1b5fd16d8..5b28bc46aeb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/ProgramDependence.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/ProgramDependence.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.graph.edges.Edge import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeSet import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection import de.fraunhofer.aisec.cpg.passes.ProgramDependenceGraphPass -import kotlin.reflect.KProperty /** The types of dependences that might be represented in the CPG */ enum class DependenceType { @@ -49,11 +48,11 @@ enum class DependenceType { */ class ProgramDependences : EdgeSet>, MirroredEdgeCollection> { - override var mirrorProperty: KProperty>> + override var mirroredCollection: (Node) -> MutableCollection> constructor( thisRef: Node, - mirrorProperty: KProperty>>, + mirroredCollection: (Node) -> MutableCollection>, outgoing: Boolean, ) : super( thisRef, @@ -64,7 +63,7 @@ class ProgramDependences : }, outgoing, ) { - this.mirrorProperty = mirrorProperty + this.mirroredCollection = mirroredCollection } override fun add(element: Edge): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/overlay/BasicBlockEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/overlay/BasicBlockEdge.kt index 9c5e7a1a6fc..2c1b1477328 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/overlay/BasicBlockEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/overlay/BasicBlockEdge.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.graph.edges.Edge import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeList import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection import de.fraunhofer.aisec.cpg.graph.overlays.BasicBlock -import kotlin.reflect.KProperty /** * An edge representing that a [Node] belongs to a [BasicBlock] or the basic block contains the @@ -56,7 +55,7 @@ class BasicBlockEdge(start: Node, end: Node) : Edge(start, end) { */ class BasicBlockEdgeList( thisRef: Node, - override var mirrorProperty: KProperty>, + override var mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean = true, ) : EdgeList(thisRef = thisRef, init = ::BasicBlockEdge, outgoing = outgoing), diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/overlay/Overlay.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/overlay/Overlay.kt index f5617636211..d1b48db806d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/overlay/Overlay.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/overlay/Overlay.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.graph.edges.Edge import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeSet import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeSingletonList import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection -import kotlin.reflect.KProperty /** * Represents an edge in a graph specifically used for overlay purposes. @@ -50,14 +49,14 @@ class OverlayEdge(start: Node, end: Node) : Edge(start, end) { * * @param thisRef The current node that the edge originates from or is associated with. * @param of The optional target node of the edge. - * @param mirrorProperty The property representing a mutable collection of mirrored overlay edges. + * @param mirroredCollection Accessor for the mirrored collection of overlay edges. * @param outgoing A flag indicating whether the edge is outgoing (default is true). * @constructor Initializes the [OverlaySingleEdge] instance with the provided parameters. */ class OverlaySingleEdge( thisRef: Node, of: Node?, - override var mirrorProperty: KProperty>, + override var mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean = true, onChange: ((old: OverlayEdge?, new: OverlayEdge?) -> Unit)? = null, ) : @@ -76,14 +75,14 @@ class OverlaySingleEdge( * incoming edge handling capabilities. * * @param thisRef The reference node that the overlays are associated with. - * @param mirrorProperty A reference to a property that mirrors the collection of overlay edges. + * @param mirroredCollection Accessor for the mirrored collection of overlay edges. * @param outgoing A boolean indicating whether the edges managed by this collection are outgoing. * @constructor Initializes the [Overlays] object with a reference node, a property for edge * mirroring, and a direction to specify outgoing or incoming edges. */ class Overlays( thisRef: Node, - override var mirrorProperty: KProperty>, + override var mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean, ) : EdgeSet(thisRef = thisRef, init = ::OverlayEdge, outgoing = outgoing), diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/scopes/Import.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/scopes/Import.kt index a467a16319e..72e0a9aca9b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/scopes/Import.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/scopes/Import.kt @@ -32,7 +32,6 @@ import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeSet import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection import de.fraunhofer.aisec.cpg.graph.scopes.NamespaceScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope -import kotlin.reflect.KProperty /** * The style of the import. This can be used to distinguish between different import modes, such as @@ -85,7 +84,7 @@ class Import(start: Scope, end: NamespaceScope, var declaration: ImportNode? = n /** A container to manage [Import] edges. */ class Imports( thisRef: Node, - override var mirrorProperty: KProperty>, + override var mirroredCollection: (Node) -> MutableCollection, outgoing: Boolean = true, ) : EdgeSet( diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Call.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Call.kt index f576e63d900..a366f624fd3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Call.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Call.kt @@ -60,7 +60,11 @@ open class Call : @PopulatedByPass(SymbolResolver::class) @Relationship(value = "INVOKES", direction = Relationship.Direction.OUTGOING) var invokeEdges: Invokes = - Invokes(this, mirrorProperty = Function::calledByEdges, outgoing = true) + Invokes( + this, + mirroredCollection = { (it as Function).calledByEdges }, + outgoing = true, + ) protected set /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Expression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Expression.kt index ed47916dec6..d8a4d34a3c4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Expression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Expression.kt @@ -121,7 +121,7 @@ abstract class Expression(usedAsExpression: Boolean = true) : override var memoryValueEdges = Dataflows( this, - mirrorProperty = HasMemoryValue::memoryValueUsageEdges, + mirroredCollection = { (it as HasMemoryValue).memoryValueUsageEdges }, outgoing = false, ) override var memoryValues by unwrapping(Expression::memoryValueEdges) @@ -129,13 +129,20 @@ abstract class Expression(usedAsExpression: Boolean = true) : /** Where the memory value of this Expression is used. */ @Relationship override var memoryValueUsageEdges = - Dataflows(this, mirrorProperty = HasMemoryValue::memoryValueEdges, outgoing = true) + Dataflows( + this, + mirroredCollection = { (it as HasMemoryValue).memoryValueEdges }, + outgoing = true, + ) override var memoryValueUsages by unwrapping(Expression::memoryValueUsageEdges) /** Each Expression also has a MemoryAddress. */ @Relationship override var memoryAddressEdges = - memoryAddressEdgesOf(mirrorProperty = MemoryAddress::usageEdges, outgoing = true) + memoryAddressEdgesOf( + mirroredCollection = { (it as MemoryAddress).usageEdges }, + outgoing = true, + ) override var memoryAddresses by unwrapping(Expression::memoryAddressEdges) override fun toString(): String { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/MemoryAddress.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/MemoryAddress.kt index 838fdd3c505..080f708afe1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/MemoryAddress.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/MemoryAddress.kt @@ -44,7 +44,7 @@ open class MemoryAddress(override var name: Name, open var isGlobal: Boolean = f @Relationship open var usageEdges = memoryAddressEdgesOf( - mirrorProperty = HasMemoryAddress::memoryAddressEdges, + mirroredCollection = { (it as HasMemoryAddress).memoryAddressEdges }, outgoing = false, ) open var usages by unwrapping(MemoryAddress::usageEdges) @@ -65,14 +65,18 @@ open class MemoryAddress(override var name: Name, open var isGlobal: Boolean = f override var memoryValueEdges = Dataflows( this, - mirrorProperty = HasMemoryValue::memoryValueUsageEdges, + mirroredCollection = { (it as HasMemoryValue).memoryValueUsageEdges }, outgoing = false, ) override var memoryValues by unwrapping(MemoryAddress::memoryValueEdges) @Relationship override var memoryValueUsageEdges = - Dataflows(this, mirrorProperty = HasMemoryValue::memoryValueEdges, outgoing = true) + Dataflows( + this, + mirroredCollection = { (it as HasMemoryValue).memoryValueEdges }, + outgoing = true, + ) override var memoryValueUsages by unwrapping(MemoryAddress::memoryValueUsageEdges) } @@ -88,7 +92,10 @@ class ParameterMemoryValue(override var name: Name) : MemoryAddress(name), HasMe * get to the parameter's address */ override var memoryAddressEdges = - memoryAddressEdgesOf(mirrorProperty = MemoryAddress::usageEdges, outgoing = true) + memoryAddressEdgesOf( + mirroredCollection = { (it as MemoryAddress).usageEdges }, + outgoing = true, + ) override var memoryAddresses by unwrapping(ParameterMemoryValue::memoryAddressEdges) } @@ -114,7 +121,10 @@ class UnknownMemoryValue( } override var memoryAddressEdges = - memoryAddressEdgesOf(mirrorProperty = MemoryAddress::usageEdges, outgoing = true) + memoryAddressEdgesOf( + mirroredCollection = { (it as MemoryAddress).usageEdges }, + outgoing = true, + ) override var memoryAddresses by unwrapping(UnknownMemoryValue::memoryAddressEdges) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/overlays/BasicBlock.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/overlays/BasicBlock.kt index 24186bd7eaa..47f4a12e5d2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/overlays/BasicBlock.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/overlays/BasicBlock.kt @@ -59,7 +59,7 @@ class BasicBlock() : OverlayNode() { @Relationship(value = "BB", direction = Relationship.Direction.INCOMING) @PopulatedByPass(BasicBlockCollectorPass::class) var nodeEdges: BasicBlockEdgeList = - BasicBlockEdgeList(this, mirrorProperty = Node::basicBlockEdges, outgoing = false) + BasicBlockEdgeList(this, mirroredCollection = { it.basicBlockEdges }, outgoing = false) protected set /** The nodes contained in this basic block. */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/NamespaceScope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/NamespaceScope.kt index a319432cdd5..ac4de3fb852 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/NamespaceScope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/NamespaceScope.kt @@ -50,7 +50,7 @@ class NamespaceScope(astNode: Namespace) : NameScope(astNode) { */ @Relationship(value = "IMPORTS_SCOPE", direction = Relationship.Direction.INCOMING) val importedByEdges: Imports = - Imports(this, mirrorProperty = Scope::importedScopeEdges, outgoing = false) + Imports(this, mirroredCollection = { (it as Scope).importedScopeEdges }, outgoing = false) /** Virtual property for accessing [importedScopeEdges] without property edges. */ val importedBy: MutableSet by unwrappingIncoming(NamespaceScope::importedByEdges) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt index 4281768978e..2317926543a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/scopes/Scope.kt @@ -103,7 +103,11 @@ sealed class Scope( @Relationship(value = "IMPORTS_SCOPE", direction = Relationship.Direction.OUTGOING) @PopulatedByPass(ImportResolver::class) val importedScopeEdges = - Imports(this, mirrorProperty = NamespaceScope::importedByEdges, outgoing = true) + Imports( + this, + mirroredCollection = { (it as NamespaceScope).importedByEdges }, + outgoing = true, + ) /** Virtual property for accessing [importedScopeEdges] without property edges. */ val importedScopes by unwrapping(Scope::importedScopeEdges) From 096cfd9accfc31390bbb19b3049a23729f63b2cf Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sat, 13 Jun 2026 08:55:45 +0200 Subject: [PATCH 07/13] Lazy assumptions and additionalProblems --- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 5 ++--- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 22 ++++++++++++++++--- .../aisec/cpg/assumptions/AssumptionTest.kt | 10 ++++----- 3 files changed, 26 insertions(+), 11 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 12cf4b7e5cc..d7fc6245067 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 @@ -1580,13 +1580,12 @@ inline fun Scope.firstScopeParentOrNull( */ val AstNode?.problems: List get() { - val relevantNodes = - this.allChildren { it is ProblemNode || it.additionalProblems.isNotEmpty() } + val relevantNodes = this.allChildren { it is ProblemNode || it.hasAdditionalProblems } val result = mutableListOf() relevantNodes.forEach { - if (it.additionalProblems.isNotEmpty()) { + if (it.hasAdditionalProblems) { result += it.additionalProblems } if (it is ProblemNode) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index c7062178f69..f2081dcfbfb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -251,7 +251,13 @@ abstract class Node() : var prevPDG by unwrapping(Node::prevPDGEdges) - @DoNotPersist override val assumptions: MutableSet = mutableSetOf() + @DoNotPersist @JsonIgnore private var assumptionsStorage: MutableSet? = null + + @DoNotPersist + override val assumptions: MutableSet + get() { + return assumptionsStorage ?: mutableSetOf().also { assumptionsStorage = it } + } /** * If a node is marked as being inferred, it means that it was created artificially and does not @@ -292,7 +298,16 @@ abstract class Node() : * Additional problem nodes. These nodes represent problems which occurred during processing of * a node (i.e. only partially processed). */ - val additionalProblems: MutableSet = mutableSetOf() + @DoNotPersist @JsonIgnore private var additionalProblemsStorage: MutableSet? = null + + val additionalProblems: MutableSet + get() { + return additionalProblemsStorage + ?: mutableSetOf().also { additionalProblemsStorage = it } + } + + val hasAdditionalProblems: Boolean + get() = additionalProblemsStorage?.isNotEmpty() == true @Relationship(value = "OVERLAY", direction = Relationship.Direction.OUTGOING) val overlayEdges: Overlays = @@ -308,7 +323,8 @@ abstract class Node() : * Currently, of the [Component]. */ override fun relevantAssumptions(): Set { - return super.relevantAssumptions() + (component?.relevantAssumptions() ?: emptySet()) + val localAssumptions = assumptionsStorage?.toSet() ?: emptySet() + return localAssumptions + (component?.relevantAssumptions() ?: emptySet()) } /** diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/assumptions/AssumptionTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/assumptions/AssumptionTest.kt index 69c84184eb1..60344253bab 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/assumptions/AssumptionTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/assumptions/AssumptionTest.kt @@ -36,12 +36,12 @@ class AssumptionTest { @Test fun testAssumptionStatus() { with(TestLanguageFrontend()) { - Assumption.states[{ it.id == Uuid.parse("00000000-0000-0000-0000-000072023357") }] = + Assumption.states[{ it.id == Uuid.parse("00000000-0000-0000-0000-000066e9d78f") }] = AssumptionStatus.Accepted val lit = newLiteral(1).assume(AssumptionType.SoundnessAssumption, "We assume 1 is 1") - assertEquals(1107044270, lit.hashCode()) - assertEquals("00000000-0000-0000-0000-000041fc27ae", lit.id.toString()) + assertEquals(1698279030, lit.hashCode()) + assertEquals("00000000-0000-0000-0000-00006539ae76", lit.id.toString()) val assumption = lit.assumptions.firstOrNull() assertNotNull(assumption) @@ -52,8 +52,8 @@ class AssumptionTest { "de.fraunhofer.aisec.cpg.assumptions.Assumption", assumption.javaClass.name, ) - assertEquals(1912746839, assumption.hashCode()) - assertEquals("00000000-0000-0000-0000-000072023357", assumption.id.toString()) + assertEquals(1726601103, assumption.hashCode()) + assertEquals("00000000-0000-0000-0000-000066e9d78f", assumption.id.toString()) assertEquals(AssumptionStatus.Accepted, assumption.status) } } From d0c2808682d7c5a1ef24d4aa43af274af5921b5a Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sat, 13 Jun 2026 09:01:56 +0200 Subject: [PATCH 08/13] Some changes in Name class --- .../de/fraunhofer/aisec/cpg/graph/Name.kt | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt index 2236739a8f4..c7ee4f8cf5d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Name.kt @@ -69,8 +69,16 @@ class Name( * this is basically a cache for [toString]. Otherwise, we would need to call [toString] a lot * of times, to implement the necessary functions for [CharSequence]. */ - private val fullName: String by lazy { - (if (parent != null) parent.toString() + delimiter else "") + localName + private var qualifiedFullNameCache: String? = null + + private fun fullName(): String { + // Most names are unqualified, so avoid any extra cache object/value in this hot case. + if (parent == null) { + return localName + } + + return qualifiedFullNameCache + ?: (parent.toString() + delimiter + localName).also { qualifiedFullNameCache = it } } public override fun clone(): Name = Name(localName, parent?.clone(), delimiter) @@ -93,13 +101,14 @@ class Name( * Returns the string representation of this name using a fully qualified name notation with the * specified [delimiter]. */ - override fun toString() = fullName + override fun toString() = fullName() - override val length = fullName.length + override val length: Int + get() = fullName().length override fun equals(other: Any?): Boolean { if (this === other) return true - if (other is String) return this.fullName == other + if (other is String) return this.toString() == other if (other is Name) return localName == other.localName && parent == other.parent && @@ -108,12 +117,12 @@ class Name( return false } - override fun get(index: Int) = fullName[index] + override fun get(index: Int) = fullName()[index] override fun hashCode() = Objects.hash(localName, parent, delimiter) override fun subSequence(startIndex: Int, endIndex: Int): CharSequence = - fullName.subSequence(startIndex, endIndex) + fullName().subSequence(startIndex, endIndex) /** * Determines if this name ends with the [ending] (i.e., the localNames match until the [ending] @@ -141,7 +150,7 @@ class Name( Name(localName.replace(oldValue, newValue), parent, delimiter) /** Compare names according to the string representation of the [fullName]. */ - override fun compareTo(other: Name) = fullName.compareTo(other.toString()) + override fun compareTo(other: Name) = toString().compareTo(other.toString()) /** * A name can be considered as "qualified", if it has any specified [parent]. Otherwise, only From 2501c1c28a266ff2282937ac24d6cacea9f2e6c5 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sat, 13 Jun 2026 09:53:44 +0200 Subject: [PATCH 09/13] Reuse name as code in Reference --- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 7 ++- .../fraunhofer/aisec/cpg/graph/NodeBuilder.kt | 13 +++++ .../fraunhofer/aisec/cpg/graph/edges/Edge.kt | 8 +++- .../aisec/cpg/graph/NodeBuilderTest.kt | 48 +++++++++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilderTest.kt 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 d7fc6245067..a381dd8e5fd 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 @@ -554,7 +554,12 @@ class Context( var steps: Int = 0, ) : HasAssumptions { - override val assumptions: MutableSet = mutableSetOf() + private var assumptionsStorage: MutableSet? = null + + override val assumptions: MutableSet + get() { + return assumptionsStorage ?: mutableSetOf().also { assumptionsStorage = it } + } fun clone(): Context { return Context(indexStack = indexStack.clone(), callStack = callStack.clone(), steps) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index 8c98c0adcef..cc32b98192b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt @@ -35,6 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.LOGGER import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log import de.fraunhofer.aisec.cpg.graph.expressions.Expression +import de.fraunhofer.aisec.cpg.graph.expressions.Reference import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.types.HasType import de.fraunhofer.aisec.cpg.helpers.getCodeOfSubregion @@ -154,6 +155,18 @@ fun Node.applyMetadata( defaultNamespace } this.name = this.newName(name, doNotPrependNamespace, namespace) + + // For most references, code and name are the same token. If their textual value matches, + // reuse the name string instance to avoid duplicate string objects. + if (this is Reference) { + val currentCode = this.code + if (currentCode != null) { + val nameAsString = this.name.toString() + if (currentCode == nameAsString) { + this.code = nameAsString + } + } + } } // Disable the type observer if the config says so. diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/Edge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/Edge.kt index d8d529273b7..cc34a758686 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/Edge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/Edge.kt @@ -57,7 +57,13 @@ abstract class Edge : Persistable, Cloneable, HasAssumptions { // Node where the edge is ingoing @JsonBackReference var end: NodeType - @DoNotPersist override val assumptions: MutableSet = mutableSetOf() + @DoNotPersist @JsonIgnore private var assumptionsStorage: MutableSet? = null + + @DoNotPersist + override val assumptions: MutableSet + get() { + return assumptionsStorage ?: mutableSetOf().also { assumptionsStorage = it } + } constructor(start: Node, end: NodeType) { this.start = start diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilderTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilderTest.kt new file mode 100644 index 00000000000..6544511ae9a --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilderTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2026, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.graph + +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.expressions.Reference +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertSame + +class NodeBuilderTest { + @Test + fun testReferenceCodeReusesNameStringWhenEqual() { + with(TestLanguageFrontend()) { + val ref = Reference() + ref.code = buildString { append("foo") } + + // applyMetadata sets a Name and should canonicalize matching Reference.code strings + ref.applyMetadata(this, name = "foo", rawNode = null, doNotPrependNamespace = true) + + assertEquals("foo", ref.code) + assertSame(ref.name.toString(), ref.code) + } + } +} From d90bdf41217994654cf2fbc230fff0100ab81b41 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sat, 13 Jun 2026 10:44:44 +0200 Subject: [PATCH 10/13] Fix llvm tests --- .../aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt | 3 ++- .../aisec/cpg/frontends/llvm/StatementHandlerTest.kt | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt index 49646c71a0f..5ef17721523 100644 --- a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt +++ b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/LLVMIRLanguageFrontendTest.kt @@ -352,7 +352,8 @@ class LLVMIRLanguageFrontendTest { // Check that the first statement is "literal_i32_i1 val_success = literal_i32_i1(*ptr, *ptr // == 5)" - val declaration = cmpxchgStatement.statements[0].declarations[0] + val declaration = + (cmpxchgStatement.statements[0] as? DeclarationHolder)?.declarations?.firstOrNull() assertIs(declaration) assertLocalName("val_success", declaration) assertLocalName("literal_i32_i1", declaration.type) diff --git a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandlerTest.kt b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandlerTest.kt index 5a3313e336b..107fad6a3d2 100644 --- a/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandlerTest.kt +++ b/cpg-language-llvm/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/llvm/StatementHandlerTest.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.frontends.llvm +import de.fraunhofer.aisec.cpg.graph.DeclarationHolder import de.fraunhofer.aisec.cpg.graph.bodyOrNull import de.fraunhofer.aisec.cpg.graph.declarations.Variable import de.fraunhofer.aisec.cpg.graph.expressions.Assign @@ -846,7 +847,8 @@ class StatementHandlerTest { requiresUintCast: Boolean, ) { // Check that the value is assigned to - val declaration = atomicrmwStatement.statements[0].declarations[0] + val declaration = + (atomicrmwStatement.statements[0] as? DeclarationHolder)?.declarations?.firstOrNull() assertIs(declaration) assertLocalName(variableName, declaration) assertLocalName("i32", declaration.type) @@ -897,7 +899,8 @@ class StatementHandlerTest { variableName: String, ) { // Check that the value is assigned to - val declaration = atomicrmwStatement.statements[0].declarations[0] + val declaration = + (atomicrmwStatement.statements[0] as? DeclarationHolder)?.declarations?.firstOrNull() assertIs(declaration) assertLocalName(variableName, declaration) assertLocalName("i32", declaration.type) From 530b2781ffd41208ae359149e78df83d5af72652 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sat, 13 Jun 2026 10:51:00 +0200 Subject: [PATCH 11/13] Identity checks in edges --- .../graph/edges/collections/EdgeCollection.kt | 18 +++++++++++++ .../cpg/graph/edges/collections/EdgeList.kt | 14 ++++++++++ .../cpg/graph/edges/collections/EdgeSet.kt | 12 +++++++++ .../collections/MirroredEdgeCollection.kt | 7 +++++ .../graph/edges/collections/EdgeListTest.kt | 27 +++++++++++++++++++ .../edges/collections/UnwrappedEdgeSetTest.kt | 27 +++++++++++++++++++ 6 files changed, 105 insertions(+) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeCollection.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeCollection.kt index 11cea070e19..e543c7a0ddf 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeCollection.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeCollection.kt @@ -116,6 +116,24 @@ interface EdgeCollection> : MutableCo return any { if (outgoing) it.end == target else it.start == target } } + /** Identity-based variant of [contains], matching only the same edge instance. */ + fun containsByIdentity(edge: EdgeType): Boolean { + return this.any { it === edge } + } + + /** Identity-based variant of [remove], removing only the same edge instance. */ + fun removeByIdentity(edge: EdgeType): Boolean { + val iterator = this.iterator() + while (iterator.hasNext()) { + if (iterator.next() === edge) { + iterator.remove() + return true + } + } + + return false + } + operator fun plusAssign(end: NodeType) { add(end) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt index fcf98b304d5..6f8eda9ba61 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt @@ -85,6 +85,10 @@ abstract class EdgeList>( return indexOfIdentity(element) != -1 } + override fun containsByIdentity(edge: EdgeType): Boolean { + return indexOfIdentity(edge) != -1 + } + override fun removeMirrorBacklink(element: EdgeType): Boolean { val idx = indexOfIdentity(element) if (idx == -1) { @@ -95,6 +99,16 @@ abstract class EdgeList>( return true } + override fun removeByIdentity(edge: EdgeType): Boolean { + val idx = indexOfIdentity(edge) + if (idx == -1) { + return false + } + + removeAt(idx) + return true + } + private fun addWithoutHooks(element: EdgeType) { if (element.index == null) { element.index = this.size diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt index 17343ce060e..be1e4373251 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt @@ -73,10 +73,22 @@ abstract class EdgeSet>( } } + override fun containsByIdentity(edge: EdgeType): Boolean { + return containsMirrorBacklinkByIdentity(edge) + } + override fun removeMirrorBacklink(element: EdgeType): Boolean { return removeByIdentityWithoutHooks(element) } + override fun removeByIdentity(edge: EdgeType): Boolean { + val removed = removeByIdentityWithoutHooks(edge) + if (removed) { + handleOnRemove(edge) + } + return removed + } + private fun addWithoutHooks(element: EdgeType): Boolean { return when { storage.many != null -> { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt index 218eb40f0fa..394b82cb49e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt @@ -53,6 +53,8 @@ interface MirroredEdgeCollection) { (mirror as MirrorBacklinkCollection).removeMirrorBacklink(edge) + } else if (mirror is EdgeCollection<*, *>) { + (mirror as EdgeCollection).removeByIdentity(edge) } else if (edge in mirror) { mirror.remove(edge) } @@ -75,6 +77,11 @@ interface MirroredEdgeCollection) { + val edgeCollection = mirror as EdgeCollection + if (!edgeCollection.containsByIdentity(edge)) { + edgeCollection.add(edge) + } } else if (edge !in mirror) { mirror.add(edge) } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeListTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeListTest.kt index 5e119f83d23..ac9c7d8a210 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeListTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeListTest.kt @@ -30,9 +30,12 @@ import de.fraunhofer.aisec.cpg.graph.AstNode import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdge import de.fraunhofer.aisec.cpg.graph.edges.ast.AstEdges +import de.fraunhofer.aisec.cpg.graph.edges.flows.EvaluationOrder import de.fraunhofer.aisec.cpg.graph.newLiteral import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue class EdgeListTest { @Test @@ -96,4 +99,28 @@ class EdgeListTest { assertEquals(emptyList(), list.unwrap()) } } + + @Test + fun testIdentityBasedOperations() { + with(TestLanguageFrontend()) { + val owner = newLiteral(0) + val target = newLiteral(1) + + val list = owner.nextEOGEdges + val edge1 = EvaluationOrder(owner, target) + val edge2 = EvaluationOrder(owner, target) + + list.add(edge1) + + // Identity checks must distinguish object instances. + assertTrue(list.containsByIdentity(edge1)) + assertFalse(list.containsByIdentity(edge2)) + + assertFalse(list.removeByIdentity(edge2)) + assertEquals(1, list.size) + + assertTrue(list.removeByIdentity(edge1)) + assertEquals(0, list.size) + } + } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/UnwrappedEdgeSetTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/UnwrappedEdgeSetTest.kt index a92bab32554..258f62c4fa9 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/UnwrappedEdgeSetTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/UnwrappedEdgeSetTest.kt @@ -27,9 +27,12 @@ package de.fraunhofer.aisec.cpg.graph.edges.collections import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.edges.flows.Dataflow import de.fraunhofer.aisec.cpg.graph.newLiteral import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue class UnwrappedEdgeSetTest { @Test @@ -81,4 +84,28 @@ class UnwrappedEdgeSetTest { assertEquals(0, node3.prevDFG.size) } } + + @Test + fun testIdentityBasedOperations() { + with(TestLanguageFrontend()) { + val node1 = newLiteral(1) + val node2 = newLiteral(2) + + val edge1 = Dataflow(node1, node2) + val edge2 = Dataflow(node1, node2) + + node1.nextDFGEdges.add(edge1) + + // Equality matches by start/end+properties, identity must still distinguish instances. + assertTrue(node1.nextDFGEdges.contains(edge2)) + assertTrue(node1.nextDFGEdges.containsByIdentity(edge1)) + assertFalse(node1.nextDFGEdges.containsByIdentity(edge2)) + + assertFalse(node1.nextDFGEdges.removeByIdentity(edge2)) + assertEquals(1, node1.nextDFGEdges.size) + + assertTrue(node1.nextDFGEdges.removeByIdentity(edge1)) + assertEquals(0, node1.nextDFGEdges.size) + } + } } From 7f452fc9722fb6e3cd68b7e127b7f4567151ec9c Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sat, 13 Jun 2026 11:52:45 +0200 Subject: [PATCH 12/13] Fix another test --- .../frontends/cxx/CXXLanguageFrontendTest.kt | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt index b3679f07ce5..2df1d2e3e20 100644 --- a/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt +++ b/cpg-language-cxx/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/cxx/CXXLanguageFrontendTest.kt @@ -383,30 +383,35 @@ internal class CXXLanguageFrontendTest : BaseTest() { ) val declFromMultiplicateExpression = - (statements[0] as DeclarationStatement).getSingleDeclarationAs(Variable::class.java) + (statements[0] as? DeclarationStatement)?.getSingleDeclarationAs( + Variable::class.java + ) assertEquals( assertResolvedType("SSL_CTX").pointer(), - declFromMultiplicateExpression.type, + declFromMultiplicateExpression?.type, ) assertLocalName("ptr", declFromMultiplicateExpression) val withInitializer = - (statements[1] as DeclarationStatement).getSingleDeclarationAs(Variable::class.java) - var initializer = withInitializer.initializer + (statements[1] as? DeclarationStatement)?.getSingleDeclarationAs( + Variable::class.java + ) + var initializer = withInitializer?.initializer assertNotNull(initializer) assertTrue(initializer is Literal<*>) assertEquals(1, initializer.value) - val twoDeclarations = statements[2].declarations + val twoDeclarations = (statements[2] as? DeclarationHolder)?.declarations + assertNotNull(twoDeclarations) assertEquals(2, twoDeclarations.size) - val b = twoDeclarations[0] as Variable - assertNotNull(b) + val b = twoDeclarations[0] + assertIs(b) assertLocalName("b", b) assertEquals(primitiveType("int").reference(POINTER), b.type) - val c = twoDeclarations[1] as Variable - assertNotNull(c) + val c = twoDeclarations[1] + assertIs(c) assertLocalName("c", c) assertEquals(primitiveType("int"), c.type) @@ -430,15 +435,16 @@ internal class CXXLanguageFrontendTest : BaseTest() { assertLocalName("ptr2", pointerWithAssign) assertLiteralValue(null, pointerWithAssign.initializer) - val classWithVariable = statements[6].declarations + val classWithVariable = (statements[6] as? DeclarationHolder)?.declarations + assertNotNull(classWithVariable) assertEquals(2, classWithVariable.size) - val classA = classWithVariable[0] as Record - assertNotNull(classA) + val classA = classWithVariable[0] + assertIs(classA) assertLocalName("A", classA) - val myA = classWithVariable[1] as Variable - assertNotNull(myA) + val myA = classWithVariable[1] + assertIs(myA) assertLocalName("myA", myA) assertEquals(classA, (myA.type as ObjectType).recordDeclaration) } From 22cbd59cb8176d4135210997ad35c84decaca762 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 15 Jun 2026 14:11:53 +0200 Subject: [PATCH 13/13] Fix tests --- cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt | 1 + .../de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt | 2 +- .../de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt | 2 +- .../aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt | 2 -- .../cpg/frontends/python/statementHandler/WithStatementTest.kt | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index f2081dcfbfb..47364294cb3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt @@ -306,6 +306,7 @@ abstract class Node() : ?: mutableSetOf().also { additionalProblemsStorage = it } } + @DoNotPersist val hasAdditionalProblems: Boolean get() = additionalProblemsStorage?.isNotEmpty() == true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt index 6f8eda9ba61..ae2af4058da 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeList.kt @@ -77,7 +77,7 @@ abstract class EdgeList>( return false } - addWithoutHooks(element) + add(element) return true } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt index be1e4373251..560e9a040c8 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSet.kt @@ -63,7 +63,7 @@ abstract class EdgeSet>( } override fun addMirrorBacklink(element: EdgeType): Boolean { - return addWithoutHooks(element) + return add(element) } override fun containsMirrorBacklinkByIdentity(element: EdgeType): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt index 394b82cb49e..57b9fba597a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/MirroredEdgeCollection.kt @@ -82,8 +82,6 @@ interface MirroredEdgeCollection().filter { it.astParent is Namespace } - val ref = result.refs["contextManager_00000000-11a2-7efe-0000-00000958dca6"] + val ref = result.refs["contextManager_00000000-11a2-7efe-0000-00000958dc87"] assertNotNull( ref, "Expected to find a reference to the context manager with a deterministic ID.",