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/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/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 12cf4b7e5cc..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) @@ -1580,13 +1585,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/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/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 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..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 @@ -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,12 +246,18 @@ 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) - @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 @@ -288,11 +298,25 @@ 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 } + } + + @DoNotPersist + val hasAdditionalProblems: Boolean + get() = additionalProblemsStorage?.isNotEmpty() == true @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) /** @@ -300,7 +324,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/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/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/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/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/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/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/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/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/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 48793af7f4c..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 @@ -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,201 @@ abstract class EdgeList>( * operations to a larger list. */ initialCapacity: Int = 1, -) : ArrayList(initialCapacity), EdgeCollection { +) : + AbstractMutableList(), + EdgeCollection, + MirrorBacklinkCollection { + + // Draft: compact 0/1/many storage to avoid allocating an ArrayList per node in the common case. + private val storage = + CompactEdgeStorage> { cap -> ArrayList(cap) } + private var cachedCapacity: Int = initialCapacity + + override val size: Int + get() = storage.size + + override fun get(index: Int): EdgeType { + return when { + storage.many != null -> storage.many!![index] + index == 0 && storage.first != null -> storage.first!! + else -> throw IndexOutOfBoundsException("Index: $index, Size: $size") + } + } override fun add(element: EdgeType): Boolean { - // Make sure, the index is always set + addWithoutHooks(element) + handleOnAdd(element) + return true + } + + override fun addMirrorBacklink(element: EdgeType): Boolean { + if (containsMirrorBacklinkByIdentity(element)) { + return false + } + + add(element) + return true + } + + override fun containsMirrorBacklinkByIdentity(element: EdgeType): Boolean { + 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) { + return false + } + + removeAtWithoutHooks(idx) + 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 } - val ok = super.add(element) - if (ok) { - handleOnAdd(element) + when { + storage.many != null -> storage.many!!.add(element) + storage.first == null -> storage.first = element + else -> { + storage.ensureMany(maxOf(cachedCapacity, 2)).add(element) + } + } + } + + override fun add(index: Int, element: EdgeType) { + if (index < 0 || index > size) { + throw IndexOutOfBoundsException("Index: $index, Size: $size") + } + + when { + storage.many != null -> storage.many!!.add(index, element) + storage.first == null -> { + if (index != 0) throw IndexOutOfBoundsException("Index: $index, Size: $size") + storage.first = element + } + else -> { + val created = storage.ensureMany(maxOf(cachedCapacity, 2)) + if (index == 0) { + created.add(0, element) + } else { + created.add(element) + } + } + } + + handleOnAdd(element) + updateIndicesFrom(index) + } + + override fun removeAt(index: Int): EdgeType { + if (index < 0 || index >= size) { + 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 -> { + val r = storage.many!!.removeAt(index) + if (storage.many!!.size == 1) { + storage.first = storage.many!![0] + storage.many = null + } + r + } + else -> { + val r = storage.first!! + storage.first = null + r + } + } + + 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 { + storage.many != null -> { + val prev = storage.many!![index] + storage.many!![index] = element + prev + } + else -> { + val prev = storage.first!! + storage.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 } - return ok + + val toRemove = elements.toSet() + var changed = false + + for (i in size - 1 downTo 0) { + if (this[i] in toRemove) { + removeAt(i) + changed = true + } + } + + 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 = storage.clearAndSnapshot() + 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 +253,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 +263,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 +294,22 @@ abstract class EdgeList>( override fun hashCode(): Int { 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 + } + + 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..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 @@ -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,166 @@ 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, + MirrorBacklinkCollection { + + // Draft: compact 0/1/many storage to avoid eagerly allocating a HashSet for singleton cases. + private val storage = CompactEdgeStorage> { cap -> HashSet(cap) } + + override val size: Int + get() = storage.size + override fun add(element: EdgeType): Boolean { - val ok = super.add(element) + val ok = addWithoutHooks(element) if (ok) { handleOnAdd(element) } 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 addMirrorBacklink(element: EdgeType): Boolean { + return add(element) + } + + override fun containsMirrorBacklinkByIdentity(element: EdgeType): Boolean { + return when { + storage.many != null -> storage.many!!.any { it === element } + else -> storage.first === element } - return ok } - override fun removeAll(elements: Collection): Boolean { - val ok = super.removeAll(elements.toSet()) - if (ok) { - elements.forEach { handleOnRemove(it) } + 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 ok + return removed + } + + private fun addWithoutHooks(element: EdgeType): Boolean { + return when { + storage.many != null -> { + storage.many!!.add(element) + } + storage.first == null -> { + storage.first = element + true + } + storage.first == element -> false + else -> { + storage.ensureMany(2).add(element) + true + } + } + } + + override fun iterator(): MutableIterator { + val singleton = storage.first + return if (storage.many != null) { + ManyIterator(storage.many!!.iterator()) + } else { + SingletonIterator(singleton) + } + } + + override fun contains(element: EdgeType): Boolean { + return storage.many?.contains(element) ?: (storage.first == element) } override fun remove(element: EdgeType): Boolean { - val ok = super.remove(element) + 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) { + compactManyIfPossible() + } + ok + } + storage.first == element -> { + storage.first = null + 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 + } + } + + 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 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() + val edges = storage.clearAndSnapshot() edges.forEach { handleOnRemove(it) } } @@ -106,4 +229,59 @@ abstract class EdgeSet>( override fun hashCode(): Int { return internalHashcode(this, outgoing) } + + private fun compactManyIfPossible() { + storage.compactManyToSingleton { it.firstOrNull() } + } + + 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()") + } + + storage.first = null + handleOnRemove(edge) + canRemove = 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..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 @@ -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,20 @@ 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 (mirror is EdgeCollection<*, *>) { + (mirror as EdgeCollection).removeByIdentity(edge) + } else if (edge in mirror) { + mirror.remove(edge) } // Execute any remaining pre actions @@ -56,21 +64,23 @@ interface MirroredEdgeCollection) { + val backlink = mirror as MirrorBacklinkCollection + if (!backlink.containsMirrorBacklinkByIdentity(edge)) { + backlink.addMirrorBacklink(edge) } - } else { - var nextOfPrev = mirrorProperty.call(edge.start) - if (edge !in nextOfPrev) { - nextOfPrev.add(edge) + } else if (mirror is EdgeCollection<*, *>) { + val edgeCollection = mirror as EdgeCollection + if (!edgeCollection.containsByIdentity(edge)) { + edgeCollection.add(edge) } } 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/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 816fc570191..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 @@ -25,10 +25,17 @@ */ 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.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 +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 +46,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 +68,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 +83,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/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/Comprehension.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Comprehension.kt index 9ac203e79b3..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 @@ -27,9 +27,18 @@ 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.addIfNotContains 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,12 +46,12 @@ 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( of = ProblemExpression("Missing variableEdge in ${this::class}"), - onChanged = { _, new -> (new?.end as? Expression)?.access = AccessValues.WRITE }, + onChanged = { _, new -> new?.end?.access = AccessValues.WRITE }, ) /** @@ -80,10 +89,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 +136,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..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 @@ -25,7 +25,9 @@ */ 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 @@ -40,7 +42,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..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 @@ -26,8 +26,17 @@ 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.addIfNotContains 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 +47,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 +79,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 +95,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..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 @@ -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 @@ -141,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) @@ -149,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 { @@ -168,22 +155,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/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-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/expressions/Switch.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/expressions/Switch.kt index 4e577eb49db..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 @@ -26,8 +26,16 @@ 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.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 +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 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 +79,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 +90,7 @@ class Switch : Expression(false), BranchingNode { selectorDeclaration, selector, statement, + locals, ) override fun getStartingPrevEOG(): Collection { @@ -93,4 +103,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..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 @@ -25,7 +25,14 @@ */ 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 +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 +41,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 +95,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 +116,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..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 @@ -28,9 +28,17 @@ 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.addIfNotContains 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 +49,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 +89,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 +106,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 } 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) 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) } } 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) + } + } +} 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..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 @@ -67,4 +70,57 @@ 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()) + } + } + + @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 ccba73cd08c..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 @@ -49,4 +52,60 @@ 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) + } + } + + @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) + } + } } 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) } 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( 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) diff --git a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/statementHandler/WithStatementTest.kt b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/statementHandler/WithStatementTest.kt index e0f9e3269c9..6363c204848 100644 --- a/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/statementHandler/WithStatementTest.kt +++ b/cpg-language-python/src/test/kotlin/de/fraunhofer/aisec/cpg/frontends/python/statementHandler/WithStatementTest.kt @@ -80,7 +80,7 @@ class WithStatementTest : BaseTest() { val blockStmts = result.statements.filterIsInstance().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.",