From 19ef696c3f4b131c78b1547609ee46a266103179 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 17 Mar 2023 11:01:36 +0100 Subject: [PATCH 01/53] Try to use a more formal worklist EOG worklist iteration for the CFSensitiveDFG --- .../aisec/cpg/analysis/ValueEvaluator.kt | 4 + .../cpg/analysis/MultiValueEvaluatorTest.kt | 4 + .../cpg/graph/statements/ForEachStatement.kt | 11 +- .../aisec/cpg/helpers/EOGWorklist.kt | 295 +++++++++++++ .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 387 +++++------------- 5 files changed, 417 insertions(+), 284 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt index 394b79a1e0..aee686017b 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/analysis/ValueEvaluator.kt @@ -77,6 +77,10 @@ open class ValueEvaluator( return evaluateInternal(node as? Node, 0) } + fun clearPath() { + path.clear() + } + /** Tries to evaluate this node. Anything can happen. */ protected open fun evaluateInternal(node: Node?, depth: Int): Any? { // Add the expression to the current path diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt index 232e3c4a1c..5399cf4c47 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/analysis/MultiValueEvaluatorTest.kt @@ -167,21 +167,25 @@ class MultiValueEvaluatorTest { printB = main.bodyOrNull(1) assertNotNull(printB) + evaluator.clearPath() value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(setOf(0, 1, 2), value.values) printB = main.bodyOrNull(2) assertNotNull(printB) + evaluator.clearPath() value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(setOf(0, 1, 2, 4), value.values) printB = main.bodyOrNull(3) assertNotNull(printB) + evaluator.clearPath() value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(setOf(-4, -2, -1, 0, 1, 2, 4), value.values) printB = main.bodyOrNull(4) assertNotNull(printB) + evaluator.clearPath() value = evaluator.evaluate(printB.arguments.firstOrNull()) as ConcreteNumberSet assertEquals(setOf(3, 6), value.values) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index 702547223d..f1f887ed02 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -26,6 +26,8 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.AccessValues +import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import java.util.Objects class ForEachStatement : Statement() { @@ -33,7 +35,14 @@ class ForEachStatement : Statement() { * This field contains the iteration variable of the loop. It can be either a new variable * declaration or a reference to an existing variable. */ - @AST var variable: Statement? = null + @AST + var variable: Statement? = null + set(value) { + if (value is DeclaredReferenceExpression) { + value.access = AccessValues.WRITE + } + field = value + } /** This field contains the iteration subject of the loop. */ @AST var iterable: Statement? = null diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt new file mode 100644 index 0000000000..e2074b4101 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2023, 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.helpers + +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import java.util.IdentityHashMap + +/** + * An abstract class representing complete lattices, i.e., an ordered structure of values of type + * [T]. Implementations of this class have to implement the comparator, the least upper bound of two + * lattices. + */ +abstract class Lattice(open val elements: T) : Comparable> { + /** + * Computes the least upper bound of this lattice and [other]. It returns a new object and does + * not modify either of the objects. + */ + abstract fun lub(other: Lattice): Lattice + + /** Duplicates the object, i.e., makes a deep copy. */ + abstract fun duplicate(): Lattice +} + +/** + * Implements the [Lattice] over a set of nodes. The lattice itself is constructed by the powerset. + */ +class PowersetLattice(override val elements: Set) : Lattice>(elements) { + override fun lub(other: Lattice>) = + PowersetLattice(other.elements.union(this.elements)) + override fun duplicate() = PowersetLattice(this.elements.toSet()) + override fun compareTo(other: Lattice>): Int { + return if (this.elements.containsAll(other.elements)) { + if (this.elements.size > other.elements.size) 1 else 0 + } else { + -1 + } + } +} + +/** + * Stores the current state. I.e., it maps a [Node] to a [Lattice]. It provides some useful + * functions e.g. to check if the mapping has to be updated (e.g. because there are new nodes or + * because a new lattice is bigger than the old one). + */ +open class State : IdentityHashMap>() { + + /** + * It updates this state by adding all new nodes in [other] to `this` and by computing the least + * upper bound for each entry. + * + * Returns this and a flag which states if there was any update necessary (or if `this` is equal + * before and after running the method). + */ + open fun lub(other: State): Pair, Boolean> { + var update = false + for ((node, newLattice) in other) { + update = push(node, newLattice) || update + } + return Pair(this, update) + } + + /** + * Checks if an update is necessary, i.e., if [other] contains nodes which are not present in + * `this` and if the lattice of a node in [other] "is bigger" than the respective lattice in + * `this`. It does not modify anything. + */ + open fun needsUpdate(other: State): Boolean { + var update = false + for ((node, newLattice) in other) { + update = update || node !in this || newLattice > this[node]!! + } + return update + } + + /** Deep copies this object. */ + open fun duplicate(): State { + val clone = State() + for ((key, value) in this) { + clone[key] = value.duplicate() + } + return clone + } + + /** + * Adds a new mapping from [newNode] to (a copy of) [newLattice] to this object if [newNode] + * does not exist in this state yet. If it already exists, it computes the least upper bound of + * [newLattice] and the current one for [newNode]. It returns if the state has changed. + */ + open fun push(newNode: Node, newLattice: Lattice?): Boolean { + if (newLattice == null) { + return false + } + if (newNode in this && this[newNode]!! >= newLattice) { + // newLattice is "smaller" than the currently stored one. We don't add it anything. + return false + } else if (newNode in this) { + // newLattice is "bigger" than the currently stored one. We update it to the least + // upper bound + this[newNode] = this[newNode]!!.lub(newLattice) + } else { + this[newNode] = newLattice.duplicate() + } + return true + } +} + +/** + * A worklist. Essentially, it stores mappings of nodes to the states which are available there and + * determines which nodes have to be analyzed. + */ +class Worklist() { + /** A mapping of nodes to the state which is currently available there. */ + var globalState: MutableMap> = mutableMapOf() + private set + + /** A list of all nodes which have already been visited. */ + private val alreadySeen = mutableListOf() + + constructor(globalState: MutableMap> = mutableMapOf()) : this() { + this.globalState = globalState + } + + /** + * The actual worklist, i.e., elements which still have to be analyzed and the state which + * should be considered there. + */ + private val nodeOrder: MutableList>> = mutableListOf() + + /** + * Adds [newNode] and the [state] to the [globalState] (i.e., computes the [State.lub] of the + * current state there and [state]). Returns true if there was an update. + */ + fun update(newNode: Node, state: State): Boolean { + val (newGlobalState, update) = globalState[newNode]?.lub(state) ?: Pair(state, true) + if (update) { + globalState[newNode] = newGlobalState + } + return update + } + + /** + * Pushes [newNode] and the [state] to the worklist or updates the currently available entry for + * the node. Returns `true` if there was a change which means that the node has to be analyzed. + * If it returns `false`, the [newNode] wasn't added to the worklist as the state didn't change. + */ + fun push(newNode: Node, state: State): Boolean { + val currentEntry = nodeOrder.find { it.first == newNode } + val update: Boolean + val newEntry = + if (currentEntry != null) { + val (newState, update2) = currentEntry.second.lub(state) + update = update2 + if (update) { + nodeOrder.remove(currentEntry) + } + Pair(currentEntry.first, newState) + } else { + update = true + Pair(newNode, state) + } + if (update) nodeOrder.add(newEntry) + return update + } + + /** Determines if there are still elements to analyze */ + fun isNotEmpty() = nodeOrder.isNotEmpty() + /** Determines if there are no more elements to analyze */ + fun isEmpty() = nodeOrder.isEmpty() + + /** Removes a [Node] from the worklist and returns the [Node] together with its [State] */ + fun pop(): Pair> { + val node = nodeOrder.removeFirst() + alreadySeen.add(node.first) + return node + } + + /** Checks if [currentNode] has already been visited before. */ + fun hasAlreadySeen(currentNode: Node) = currentNode in alreadySeen + + /** Computes the meet over paths for all the states in [globalState]. */ + fun mop(): State { + val firstKey = globalState.keys.firstOrNull() + val state = globalState[firstKey] + for ((_, v) in globalState) { + state?.lub(v) + } + + return state ?: State() + } +} + +/** A very simple implementation of the worklist algorithm. */ +class EOGWorklist { + /** + * Iterates through the worklist of the Evaluation Order Graph starting at [startNode] and with + * the [State] [startState]. For each node, the [transformation] is applied which should update + * the state. + * + * [transformation] receives the current [Node] popped from the worklist, the [State] at this + * node which is considered for this analysis and even the current [Worklist]. The worklist is + * given if we have to add more elements out-of-order e.g. because the EOG is traversed in an + * order which is not useful for this analysis. The [transformation] has to return the updated + * [State] and an indication if we expect that the state has changed. This is necessary because + * not every transition in the EOG will really lead to an update of the current state depending + * on the analysis. + */ + fun iterateEOG( + startNode: Node, + startState: State, + transformation: (Node, State, Worklist) -> Pair, Boolean> + ): State { + val worklist = Worklist(mutableMapOf(Pair(startNode, startState))) + worklist.push(startNode, startState) + + while (worklist.isNotEmpty()) { + val (nextNode, state) = worklist.pop() + + val (newState, expectedUpdate) = transformation(nextNode, state.duplicate(), worklist) + if (worklist.update(nextNode, newState) || !expectedUpdate) { + nextNode.nextEOG.forEach { worklist.push(it, newState.duplicate()) } + } + } + return worklist.mop() + } +} + +/** + * A state which actually holds a state for all nodes, one only for declarations and one for + * ReturnStatements. + */ +class DFGPassState( + /** A mapping of a [Node] to its [Lattice]. */ + var generalState: State = State(), + /** A mapping of [Declaration] to its [Lattice]. */ + var declarationsState: State = State(), + /** The [returnStatements] which are reachable. */ + var returnStatements: State = State() +) : State() { + override fun duplicate(): DFGPassState { + return DFGPassState(generalState.duplicate(), declarationsState.duplicate()) + } + + override fun lub(other: State): Pair, Boolean> { + return if (other is DFGPassState) { + val (_, generalUpdate) = generalState.lub(other.generalState) + val (_, declUpdate) = declarationsState.lub(other.declarationsState) + Pair(this, generalUpdate || declUpdate) + } else { + val (_, generalUpdate) = generalState.lub(other) + Pair(this, generalUpdate) + } + } + + override fun needsUpdate(other: State): Boolean { + return if (other is DFGPassState) { + generalState.needsUpdate(other.generalState) || + declarationsState.needsUpdate(other.declarationsState) + } else { + generalState.needsUpdate(other) + } + } + + override fun push(newNode: Node, newLattice: Lattice?): Boolean { + return generalState.push(newNode, newLattice) + } + + /** Pushes the [newNode] and its [newLattice] to the [declarationsState]. */ + fun pushToDeclarationsState(newNode: Declaration, newLattice: Lattice?): Boolean { + return declarationsState.push(newNode, newLattice) + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 72fd969122..a4882f9019 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.Declaration -import de.fraunhofer.aisec.cpg.graph.declarations.FieldDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties @@ -37,7 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator -import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker.IterativeGraphWalker +import de.fraunhofer.aisec.cpg.helpers.* import de.fraunhofer.aisec.cpg.passes.order.DependsOn /** @@ -48,13 +47,13 @@ import de.fraunhofer.aisec.cpg.passes.order.DependsOn */ @DependsOn(EvaluationOrderGraphPass::class) @DependsOn(DFGPass::class) -open class ControlFlowSensitiveDFGPass : Pass() { +class ControlFlowSensitiveDFGPass : Pass() { override fun cleanup() { // Nothing to do } override fun accept(translationResult: TranslationResult) { - val walker = IterativeGraphWalker() + val walker = SubgraphWalker.IterativeGraphWalker() walker.registerOnNodeVisit(::handle) for (tu in translationResult.translationUnits) { walker.iterate(tu) @@ -69,7 +68,20 @@ open class ControlFlowSensitiveDFGPass : Pass() { protected fun handle(node: Node) { if (node is FunctionDeclaration) { clearFlowsOfVariableDeclarations(node) - handleStatementHolder(node) + val finalState = EOGWorklist().iterateEOG(node, DFGPassState(), ::transfer) + + removeUnreachableImplicitReturnStatement( + node, + (finalState as DFGPassState).returnStatements.values.flatMap { + it.elements.filterIsInstance() + } + ) + + for ((key, value) in (finalState as DFGPassState).generalState) { + key.addAllPrevDFG( + value.elements.filterNot { it is VariableDeclaration && key == it } + ) + } } } @@ -84,62 +96,43 @@ open class ControlFlowSensitiveDFGPass : Pass() { } } - /** - * Performs a forward analysis through the EOG to collect all possible writes to a variable and - * adds them to the DFG edges to the read operations of that variable. We differentiate between - * the flows based on the following types of statements/expressions: - * - VariableDeclaration with an initializer - * - Unary operators ++ and -- - * - Assignments of the form "variable = rhs" - * - Assignments with an operation e.g. of the form "variable += rhs" - * - Read operations on a variable - */ - private fun handleStatementHolder(node: Node) { - // The list of nodes that we have to consider and the last write operations to the different - // variables. - val worklist = - mutableListOf>>>( - Pair(node, mutableMapOf()) - ) - - val alreadyProcessed = mutableSetOf>>() - - // Different points which could be the cause of a loop (in a non-broken program). We - // consider ForStatements, WhileStatements, ForEachStatements, DoStatements and - // GotoStatements - val loopPoints = mutableMapOf>>() + companion object { + @JvmStatic + fun isLoopPoint(node: Node) = + node is ForStatement || + node is WhileStatement || + node is ForEachStatement || + node is DoStatement || + node is GotoStatement || + node is ContinueStatement + + @JvmStatic + fun transfer( + currentNode: Node, + state: State>, + worklist: Worklist> + ): Pair>, Boolean> { + // We will set this if we write to a variable + val writtenDecl: Declaration? - val returnStatements = mutableSetOf() + var expectedUpdate = isLoopPoint(currentNode) - // Iterate through the worklist - while (worklist.isNotEmpty()) { - // The node we will analyze now and the map of the last write statements to a variable. - val (currentNode, previousWrites) = worklist.removeFirst() - if ( - !alreadyProcessed.add( - Pair(currentNode, previousWrites.mapValues { (_, v) -> v.last() }) - ) - ) { - // The entry did already exist. This means that the changes won't have any effects - // and we don't have to run the loop. - continue - } - // We will set this if we write to a variable - var writtenDecl: Declaration? = null - var currentWritten = currentNode + val doubleState = state as DFGPassState val initializer = (currentNode as? VariableDeclaration)?.initializer if (initializer != null) { + expectedUpdate = true // A variable declaration with an initializer => The initializer flows to the // declaration. - currentNode.addPrevDFG(initializer) + // We also wrote something to this variable declaration + state.push(currentNode, PowersetLattice(setOf(initializer))) - // We wrote something to this variable declaration - writtenDecl = currentNode - - // Add the node to the list of previous write nodes in this path - previousWrites[currentNode] = mutableListOf(currentNode) + doubleState.pushToDeclarationsState( + currentNode, + PowersetLattice(setOf(currentNode)) + ) } else if (isIncOrDec(currentNode)) { + expectedUpdate = true // Increment or decrement => Add the prevWrite of the input to the input. After the // operation, the prevWrite of the input's variable is this node. val input = (currentNode as UnaryOperator).input as DeclaredReferenceExpression @@ -147,35 +140,27 @@ open class ControlFlowSensitiveDFGPass : Pass() { writtenDecl = input.refersTo if (writtenDecl != null) { - previousWrites[writtenDecl]?.lastOrNull()?.let { input.addPrevDFG(it) } - - // TODO: Do we want to have a flow from the input back to the input? This can - // cause problems if the DFG is not iterated through appropriately. The - // following line would remove it: - // currentNode.removeNextDFG(input) - - // Add the whole node to the list of previous write nodes in this path. This - // prevents some weird circular dependencies. - previousWrites - .computeIfAbsent(writtenDecl, ::mutableListOf) - .add(currentNode.input) - currentWritten = currentNode.input + state.push(input, doubleState.declarationsState[writtenDecl]) + doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(input)) } } else if (isSimpleAssignment(currentNode)) { + expectedUpdate = true // We write to the target => the rhs flows to the lhs - (currentNode as BinaryOperator).rhs.let { currentNode.lhs.addPrevDFG(it) } + state.push( + (currentNode as BinaryOperator).lhs, + PowersetLattice(setOf(currentNode.rhs)) + ) // Only the lhs is the last write statement here and the variable which is written // to. writtenDecl = (currentNode.lhs as DeclaredReferenceExpression).refersTo if (writtenDecl != null) { - previousWrites - .computeIfAbsent(writtenDecl, ::mutableListOf) - .add(currentNode.lhs as DeclaredReferenceExpression) - currentWritten = currentNode.lhs as DeclaredReferenceExpression + doubleState.declarationsState[writtenDecl] = + PowersetLattice(setOf(currentNode.lhs)) } } else if (isCompoundAssignment(currentNode)) { + expectedUpdate = true // We write to the lhs, but it also serves as an input => We first get all previous // writes to the lhs and then add the flow from lhs and rhs to the current node. @@ -185,31 +170,29 @@ open class ControlFlowSensitiveDFGPass : Pass() { if (writtenDecl != null) { // Data flows from the last writes to the lhs variable to this node - previousWrites[writtenDecl]?.lastOrNull()?.let { - currentNode.lhs.addPrevDFG(it) - } - currentNode.addPrevDFG(currentNode.lhs) - + state.push(currentNode.lhs, doubleState.declarationsState[writtenDecl]) + state.push(currentNode, PowersetLattice(setOf(currentNode.lhs))) // Data flows from whatever is the rhs to this node - currentNode.rhs.let { currentNode.addPrevDFG(it) } + state.push(currentNode, PowersetLattice(setOf(currentNode.rhs))) // TODO: Similar to the ++ case: Should the DFG edge go back to the reference? // If it shouldn't, remove the following statement: - currentNode.lhs.addPrevDFG(currentNode) + state.push(currentNode.lhs, PowersetLattice(setOf(currentNode))) // The whole current node is the place of the last update, not (only) the lhs! - previousWrites - .computeIfAbsent(writtenDecl, ::mutableListOf) - .add(currentNode.lhs) - currentWritten = currentNode.lhs + doubleState.declarationsState[writtenDecl] = + PowersetLattice(setOf(currentNode.lhs)) } } else if ((currentNode as? DeclaredReferenceExpression)?.access == AccessValues.READ) { - // We only read the variable => Get previous write which have been collected in the - // other steps - previousWrites[currentNode.refersTo]?.lastOrNull()?.let { - currentNode.addPrevDFG(it) + // We can only find a change if there's a state for the variable + doubleState.declarationsState[currentNode.refersTo]?.let { + expectedUpdate = true + // We only read the variable => Get previous write which have been collected in + // the other steps + state.push(currentNode, it) } } else if (currentNode is ForEachStatement && currentNode.variable != null) { + expectedUpdate = true // The VariableDeclaration in the ForEachStatement doesn't have an initializer, so // the "normal" case won't work. We handle this case separately here... // This is what we write to the declaration @@ -235,18 +218,7 @@ open class ControlFlowSensitiveDFGPass : Pass() { } } - currentNode.variable?.let { currentWritten = it } - if (writtenTo is DeclaredReferenceExpression) { - if (currentNode !in loopPoints) { - // We haven't been here before, so there's no chance we added something - // already. However, as the variable is processed before, we have already - // added the DFG edge from the VariableDeclaration to the - // DeclaredReferenceExpression. This doesn't make any sense, so we have to - // remove it again. - writtenTo.removePrevDFG(writtenDecl) - } - // This is a special case: We add the nextEOGEdge which goes out of the loop but // with the old previousWrites map. val nodesOutsideTheLoop = @@ -257,38 +229,48 @@ open class ControlFlowSensitiveDFGPass : Pass() { } nodesOutsideTheLoop .map { it.end } - .forEach { worklist.add(Pair(it, copyMap(previousWrites, it))) } + .forEach { worklist.push(it, state.duplicate()) } } - iterable?.let { writtenTo?.addPrevDFG(it) } - - if (writtenDecl != null && writtenTo != null) { - // Add the variable declaration (or the reference) to the list of previous write - // nodes in this path - previousWrites.computeIfAbsent(writtenDecl, ::mutableListOf).add(writtenTo) + iterable?.let { + writtenTo?.let { + state.push(writtenTo, PowersetLattice(setOf(iterable))) + // Add the variable declaration (or the reference) to the list of previous + // write nodes in this path + state.declarationsState[writtenDecl] = PowersetLattice(setOf(writtenTo)) + } + } + } else if (currentNode is FunctionDeclaration) { + // We have to add the parameters + currentNode.parameters.forEach { + doubleState.declarationsState.push(it, PowersetLattice(setOf(it))) } } else if (currentNode is ReturnStatement) { - returnStatements.add(currentNode) - } - - // Check for loops: No loop statement with the same state as before and no write which - // is already in the current chain of writes too often (=twice). - if ( - !loopDetection(currentNode, writtenDecl, currentWritten, previousWrites, loopPoints) - ) { - // We add all the next steps in the eog to the worklist unless the exact same thing - // is already included in the list. - currentNode.nextEOGEdges - .filter { it.getProperty(Properties.UNREACHABLE) != true } - .map { it.end } - .forEach { - val newPair = Pair(it, copyMap(previousWrites, it)) - if (!worklistHasSimilarPair(worklist, newPair)) worklist.add(newPair) - } + doubleState.returnStatements.push(currentNode, PowersetLattice(setOf(currentNode))) } + return Pair(state, expectedUpdate) } - removeUnreachableImplicitReturnStatement(node, returnStatements) + /** + * Checks if the node performs an operation and an assignment at the same time e.g. with the + * operators +=, -=, *=, ... + */ + private fun isCompoundAssignment(currentNode: Node) = + currentNode is BinaryOperator && + currentNode.operatorCode in BinaryOperator.compoundOperators && + (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null + + /** Checks if the node is a simple assignment of the form `var = ...` */ + private fun isSimpleAssignment(currentNode: Node) = + currentNode is BinaryOperator && + currentNode.operatorCode == "=" && + (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null + + /** Checks if the node is an increment or decrement operator (e.g. i++, i--, ++i, --i) */ + private fun isIncOrDec(currentNode: Node) = + currentNode is UnaryOperator && + (currentNode.operatorCode == "++" || currentNode.operatorCode == "--") && + (currentNode.input as? DeclaredReferenceExpression)?.refersTo != null } /** @@ -297,7 +279,7 @@ open class ControlFlowSensitiveDFGPass : Pass() { */ private fun removeUnreachableImplicitReturnStatement( node: Node, - reachableReturnStatements: MutableSet + reachableReturnStatements: Collection ) { val lastStatement = ((node as? FunctionDeclaration)?.body as? CompoundStatement)?.statements?.lastOrNull() @@ -308,165 +290,4 @@ open class ControlFlowSensitiveDFGPass : Pass() { ) lastStatement.removeNextDFG(node) } - - /** - * Determines if there's an item in the [worklist] which has the same last write for each - * declaration in the [newPair]. If this is the case, we can ignore it because all that changed - * was the path through the EOG to reach this state but apparently, all the writes in the - * different branches are obsoleted by one common write access which happens afterwards. - */ - private fun worklistHasSimilarPair( - worklist: MutableList>>>, - newPair: Pair>> - ): Boolean { - // We collect all states in the worklist which are only a subset of the new pair. We will - // remove them to avoid unnecessary computations. - val subsets = mutableSetOf>>>() - val newPairLastMap = newPair.second.mapValues { (_, v) -> v.last() } - for (existingPair in worklist) { - if (existingPair.first == newPair.first) { - // The next nodes match. Now check the last writes for each declaration. - var allWritesMatch = true - var allExistingWritesMatch = true - for ((lastWriteDecl, lastWrite) in newPairLastMap) { - - // We ignore FieldDeclarations because we cannot be sure how interprocedural - // data flows affect the field. Handling them in the state would only blow up - // the number of paths unnecessarily. - if (lastWriteDecl is FieldDeclaration) continue - - // Will we generate the same "prev DFG" with the item that is already in the - // list? - allWritesMatch = - allWritesMatch && existingPair.second[lastWriteDecl]?.last() == lastWrite - // All last writes which exist in the "existing pair" match but we have new - // declarations in the current one - allExistingWritesMatch = - allExistingWritesMatch && - (lastWriteDecl !in existingPair.second || - existingPair.second[lastWriteDecl]?.last() == lastWrite) - } - // We found a matching pair in the worklist? Done. Otherwise, maybe there's another - // pair... - if (allWritesMatch) return true - // The new state is a superset of the old one? We will add the missing pieces to the - // old one. - if (allExistingWritesMatch) { - subsets.add(existingPair) - } - } - } - - // Check the "subsets" again, and add the missing declarations - if (subsets.isNotEmpty()) { - for (s in subsets) { - for ((k, v) in newPair.second) { - if (k !in s.second) { - s.second[k] = v - } - } - } - return true // We cover it in the respective subsets, so do not add this state again. - } - - return false - } - - /** - * Checks if the node performs an operation and an assignment at the same time e.g. with the - * operators +=, -=, *=, ... - */ - private fun isCompoundAssignment(currentNode: Node) = - currentNode is BinaryOperator && - currentNode.operatorCode in BinaryOperator.compoundOperators && - (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null - - /** Checks if the node is a simple assignment of the form `var = ...` */ - private fun isSimpleAssignment(currentNode: Node) = - currentNode is BinaryOperator && - currentNode.operatorCode == "=" && - (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null - - /** Checks if the node is an increment or decrement operator (e.g. i++, i--, ++i, --i) */ - private fun isIncOrDec(currentNode: Node) = - currentNode is UnaryOperator && - (currentNode.operatorCode == "++" || currentNode.operatorCode == "--") && - (currentNode.input as? DeclaredReferenceExpression)?.refersTo != null - - /** - * Determines if the [currentNode] is a loop point has already been visited with the exact same - * state before. Changes the state saved in the [loopPoints] by adding the current - * [previousWrites]. - * - * @return true if a loop was detected, false otherwise - */ - private fun loopDetection( - currentNode: Node, - writtenDecl: Declaration?, - currentWritten: Node, - previousWrites: MutableMap>, - loopPoints: MutableMap>> - ): Boolean { - if ( - currentNode is ForStatement || - currentNode is WhileStatement || - currentNode is ForEachStatement || - currentNode is DoStatement || - currentNode is GotoStatement || - currentNode is ContinueStatement - ) { - // Loop detection: This is a point which could serve as a loop, so we check all - // states which we have seen before in this place. - val state = loopPoints.computeIfAbsent(currentNode) { mutableMapOf() } - if ( - previousWrites.all { (decl, prevs) -> - (state[decl]?.contains(prevs.last())) == true - } - ) { - // The current state of last write operations has already been seen before => - // Nothing new => Do not add the next eog steps! - return true - } - // Add the current state for future loop detections. - previousWrites.forEach { (decl, prevs) -> - state.computeIfAbsent(decl, ::mutableSetOf).add(prevs.last()) - } - } - return writtenDecl != null && - ((previousWrites[writtenDecl]?.filter { it == currentWritten }?.size ?: 0) >= 2) - } - - /** - * Copies the map. We remove all the declarations which are no longer relevant because they are - * in a child scope of the next hop. - */ - private fun copyMap( - map: Map>, - nextNode: Node - ): MutableMap> { - val result = mutableMapOf>() - for ((k, v) in map) { - if ( - nextNode.scope == k.scope || - !nextNode.hasOuterScopeOf(k) || - ((nextNode is ForStatement || nextNode is ForEachStatement) && - k.scope?.parent == nextNode.scope) - ) { - result[k] = mutableListOf() - result[k]?.addAll(v) - } - } - return result - } - - private fun Node.hasOuterScopeOf(node: Node): Boolean { - var parentScope = node.scope?.parent - while (parentScope != null) { - if (this.scope == parentScope) { - return true - } - parentScope = parentScope.parent - } - return false - } } From 50e1d48d9a308fa9a5c1c0fbb53ffa119d814be1 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sat, 18 Mar 2023 09:48:44 +0100 Subject: [PATCH 02/53] Add new SplitsControlFlow interface --- .../aisec/cpg/graph/SplitsControlFlow.kt | 38 +++++++++++++++++++ .../aisec/cpg/graph/statements/CatchClause.kt | 12 +++++- .../cpg/graph/statements/ForEachStatement.kt | 12 +++++- .../cpg/graph/statements/ForStatement.kt | 12 +++++- .../aisec/cpg/graph/statements/IfStatement.kt | 12 +++++- .../cpg/graph/statements/SwitchStatement.kt | 12 +++++- .../cpg/graph/statements/WhileStatement.kt | 12 +++++- .../cpg/passes/EvaluationOrderGraphPass.kt | 1 + 8 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt new file mode 100644 index 0000000000..52039825f6 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 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 + +/** A node triggering a conditional execution of other code. */ +interface SplitsControlFlow { + /** The node which affects the next EOG edge. Typically, this is a condition or similar. */ + val splittingNode: Node? + + /** + * The nodes which can be conditionally visited depending on [splittingNode]. It does only + * contain the "next hop", not the child nodes of the subtree. + */ + val affectedNodes: MutableList +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt index fd67cd5ba2..0bca30731b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt @@ -25,15 +25,25 @@ */ package de.fraunhofer.aisec.cpg.graph.statements +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.Objects -class CatchClause : Statement() { +class CatchClause : Statement(), SplitsControlFlow { @AST var parameter: VariableDeclaration? = null @AST var body: CompoundStatement? = null + override val splittingNode: Node? + get() = parameter + + @PopulatedByPass(EvaluationOrderGraphPass::class) + override val affectedNodes = mutableListOf() + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is CatchClause) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index f1f887ed02..5ceb4a288b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -25,12 +25,16 @@ */ package de.fraunhofer.aisec.cpg.graph.statements +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.AccessValues +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.Objects -class ForEachStatement : Statement() { +class ForEachStatement : Statement(), SplitsControlFlow { /** * This field contains the iteration variable of the loop. It can be either a new variable * declaration or a reference to an existing variable. @@ -50,6 +54,12 @@ class ForEachStatement : Statement() { /** This field contains the body of the loop. */ @AST var statement: Statement? = null + override val splittingNode: Node? + get() = iterable + + @PopulatedByPass(EvaluationOrderGraphPass::class) + override val affectedNodes = mutableListOf() + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ForEachStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt index 939d8b0238..6527743171 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt @@ -25,12 +25,16 @@ */ package de.fraunhofer.aisec.cpg.graph.statements +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.* -class ForStatement : Statement() { +class ForStatement : Statement(), SplitsControlFlow { @AST var statement: Statement? = null @AST var initializerStatement: Statement? = null @@ -41,6 +45,12 @@ class ForStatement : Statement() { @AST var iterationStatement: Statement? = null + override val splittingNode: Node? + get() = condition ?: conditionDeclaration + + @PopulatedByPass(EvaluationOrderGraphPass::class) + override val affectedNodes = mutableListOf() + override fun equals(other: Any?): Boolean { if (this === other) { return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt index 53dcc68045..b0c8d6a189 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt @@ -25,15 +25,19 @@ */ package de.fraunhofer.aisec.cpg.graph.statements +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.ArgumentHolder +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a condition control flow statement, usually indicating by `If`. */ -class IfStatement : Statement(), ArgumentHolder { +class IfStatement : Statement(), SplitsControlFlow, ArgumentHolder { /** C++ initializer statement. */ @AST var initializerStatement: Statement? = null @@ -43,6 +47,12 @@ class IfStatement : Statement(), ArgumentHolder { /** The condition to be evaluated. */ @AST var condition: Expression? = null + override val splittingNode: Node? + get() = condition ?: conditionDeclaration + + @PopulatedByPass(EvaluationOrderGraphPass::class) + override val affectedNodes = mutableListOf() + /** C++ constexpr construct. */ var isConstExpression = false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt index 5699a85358..fca5b1d9b6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt @@ -25,9 +25,13 @@ */ package de.fraunhofer.aisec.cpg.graph.statements +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.Objects /** @@ -35,7 +39,7 @@ import java.util.Objects * and default statements. Break statements break out of the switch and labeled breaks in JAva are * handled properly. */ -class SwitchStatement : Statement() { +class SwitchStatement : Statement(), SplitsControlFlow { /** Selector that determines the case/default statement of the subsequent execution */ @AST var selector: Expression? = null @@ -51,6 +55,12 @@ class SwitchStatement : Statement() { */ @AST var statement: Statement? = null + override val splittingNode: Node? + get() = selector + + @PopulatedByPass(EvaluationOrderGraphPass::class) + override val affectedNodes = mutableListOf() + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is SwitchStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt index ceaea1b8aa..92225c036f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt @@ -25,14 +25,18 @@ */ package de.fraunhofer.aisec.cpg.graph.statements +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a conditional loop statement of the form: `while(...){...}`. */ -class WhileStatement : Statement() { +class WhileStatement : Statement(), SplitsControlFlow { /** C++ allows defining a declaration instead of a pure logical expression as condition */ @AST var conditionDeclaration: Declaration? = null @@ -45,6 +49,12 @@ class WhileStatement : Statement() { */ @AST var statement: Statement? = null + override val splittingNode: Node? + get() = condition ?: conditionDeclaration + + @PopulatedByPass(EvaluationOrderGraphPass::class) + override val affectedNodes = mutableListOf() + override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index 86fd9e8cd3..ed681e30cb 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -892,6 +892,7 @@ open class EvaluationOrderGraphPass : Pass() { } else { openBranchNodes.addAll(openConditionEOGs) } + node.affectedNodes.addAll(node.nextEOG) scopeManager.leaveScope(node) setCurrentEOGs(openBranchNodes) } From 67086bc4c30b2ad97da3e472e11da4e49104a19d Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Sun, 19 Mar 2023 11:14:59 +0100 Subject: [PATCH 03/53] New unreachable EOG edges pass --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 191 +++++++++++++----- .../cpg/passes/UnreachableEOGPassTest.kt | 47 ++++- .../aisec/cpg/helpers/EOGWorklist.kt | 110 +++++----- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 54 ++++- 4 files changed, 279 insertions(+), 123 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index 99db6a551d..d8456cc7c0 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -28,12 +28,13 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.analysis.ValueEvaluator import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement +import de.fraunhofer.aisec.cpg.helpers.* import de.fraunhofer.aisec.cpg.passes.order.DependsOn -import de.fraunhofer.aisec.cpg.processing.IVisitor -import de.fraunhofer.aisec.cpg.processing.strategy.Strategy /** * A [Pass] which uses a simple logic to determine constant values and mark unreachable code regions @@ -41,59 +42,153 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy */ @DependsOn(ControlFlowSensitiveDFGPass::class) class UnreachableEOGPass : Pass() { - override fun accept(t: TranslationResult) { - for (tu in t.translationUnits) { - tu.accept( - Strategy::AST_FORWARD, - object : IVisitor() { - override fun visit(t: Node) { - when (t) { - is IfStatement -> handleIfStatement(t) - is WhileStatement -> handleWhileStatement(t) - } - - super.visit(t) - } - } - ) - } + override fun cleanup() { + // Nothing to do } - private fun handleIfStatement(n: IfStatement) { - val evalResult = ValueEvaluator().evaluate(n.condition) - if (evalResult is Boolean && evalResult == true) { - n.nextEOGEdges - .firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 } - ?.addProperty(Properties.UNREACHABLE, true) - } else if (evalResult is Boolean && evalResult == false) { - n.nextEOGEdges - .firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 } - ?.addProperty(Properties.UNREACHABLE, true) + override fun accept(translationResult: TranslationResult) { + val walker = SubgraphWalker.IterativeGraphWalker() + walker.registerOnNodeVisit(::handle) + for (tu in translationResult.translationUnits) { + walker.iterate(tu) } } - private fun handleWhileStatement(n: WhileStatement) { - /* - * Note: It does not understand that code like - * x = true; while(x) {...; x = false;} - * makes the loop execute at least once. - * Apparently, the CPG does not offer the required functionality to - * differentiate between the first and subsequent evaluations of the - * condition. - */ - val evalResult = ValueEvaluator().evaluate(n.condition) - if (evalResult is Boolean && evalResult == true) { - n.nextEOGEdges - .firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 } - ?.addProperty(Properties.UNREACHABLE, true) - } else if (evalResult is Boolean && evalResult == false) { - n.nextEOGEdges - .firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 } - ?.addProperty(Properties.UNREACHABLE, true) + /** + * We perform the actions for each [FunctionDeclaration]. + * + * @param node every node in the TranslationResult + */ + protected fun handle(node: Node) { + if (node is FunctionDeclaration) { + val startState = UnreachabilityState() + for (firstEdge in node.nextEOGEdges) { + startState.push(firstEdge, ReachabilityLattice(Reachability.REACHABLE)) + } + val finalState = EOGWorklist().iterateEOG(node.nextEOGEdges, startState, ::transfer) + + for ((key, value) in finalState) { + if (value.elements == Reachability.UNREACHABLE) { + key.addProperty(Properties.UNREACHABLE, true) + } + } } } - override fun cleanup() { - // nothing to do + companion object { + @JvmStatic + fun transfer( + currentEdge: PropertyEdge, + currentState: State, Reachability>, + currentWorklist: Worklist, Reachability> + ): Pair, Reachability>, Boolean> { + val currentNode = currentEdge.end + if (currentNode is IfStatement) { + handleIfStatement(currentEdge, currentNode, currentState) + } else if (currentNode is WhileStatement) { + handleWhileStatement(currentEdge, currentNode, currentState) + } else { + // For all other edges, we simply propagate the reachability property of the edge + // which made us come here. + currentNode.nextEOGEdges.forEach { + currentState.push(it, currentState[currentEdge]) + } + } + + return Pair(currentState, true) + } + + private fun handleIfStatement( + enteringEdge: PropertyEdge, + n: IfStatement, + state: State, Reachability> + ) { + val evalResult = ValueEvaluator().evaluate(n.condition) + + val (unreachableEdge, remainingEdges) = + if (evalResult is Boolean && evalResult == true) { + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 } + ) + } else if (evalResult is Boolean && evalResult == false) { + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 } + ) + } else { + Pair(null, n.nextEOGEdges) + } + + if (unreachableEdge != null) { + // This edge is definitely unreachable + state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE)) + } + + // For all other edges, we simply propagate the reachability property of the edge which + // made us come here. + remainingEdges.forEach { state.push(it, state[enteringEdge]) } + } + + private fun handleWhileStatement( + enteringEdge: PropertyEdge, + n: WhileStatement, + state: State, Reachability> + ) { + /* + * Note: It does not understand that code like + * x = true; while(x) {...; x = false;} + * makes the loop execute at least once. + * Apparently, the CPG does not offer the required functionality to + * differentiate between the first and subsequent evaluations of the + * condition. + */ + val evalResult = ValueEvaluator().evaluate(n.condition) + + val (unreachableEdge, remainingEdges) = + if (evalResult is Boolean && evalResult == true) { + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 } + ) + } else if (evalResult is Boolean && evalResult == false) { + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 } + ) + } else { + Pair(null, n.nextEOGEdges) + } + + if (unreachableEdge != null) { + // This edge is definitely unreachable + state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE)) + } + + // For all other edges, we simply propagate the reachability property of the edge which + // made us come here. + remainingEdges.forEach { state.push(it, state[enteringEdge]) } + } } } + +/** Implements the [Lattice] over reachability properties: TOP | REACHABLE | UNREACHABLE | BOTTOM */ +class ReachabilityLattice(override val elements: Reachability) : Lattice(elements) { + override fun lub(other: Lattice) = + ReachabilityLattice(maxOf(this.elements, other.elements)) + override fun duplicate() = ReachabilityLattice(this.elements) + override fun compareTo(other: Lattice) = this.elements.compareTo(other.elements) +} + +enum class Reachability { + BOTTOM, + UNREACHABLE, + REACHABLE, + TOP +} + +/** + * A state which actually holds a state for all [PropertyEdge]s, one only for declarations and one + * for ReturnStatements. + */ +class UnreachabilityState : State, Reachability>() diff --git a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt index 78f1371dfc..140beefbfd 100644 --- a/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt +++ b/cpg-analysis/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPassTest.kt @@ -34,10 +34,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.statements.IfStatement import de.fraunhofer.aisec.cpg.graph.statements.WhileStatement import java.nio.file.Path -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue +import kotlin.test.* import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInstance @@ -80,8 +77,46 @@ class UnreachableEOGPassTest { val ifStatement = method.bodyOrNull() assertNotNull(ifStatement) - assertFalse(ifStatement.nextEOGEdges[0].getProperty(Properties.UNREACHABLE) as Boolean) - assertTrue(ifStatement.nextEOGEdges[1].getProperty(Properties.UNREACHABLE) as Boolean) + // Check if the then-branch is set as reachable including all the edges until reaching the + // print + val thenDecl = ifStatement.nextEOGEdges[0] + assertFalse(thenDecl.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, thenDecl.end.nextEOGEdges.size) + // The "++" + val incOp = thenDecl.end.nextEOGEdges[0] + assertFalse(incOp.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, incOp.end.nextEOGEdges.size) + // The compoundStmt + val thenCompound = incOp.end.nextEOGEdges[0] + assertFalse(thenCompound.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, thenCompound.end.nextEOGEdges.size) + // There's the outgoing EOG edge to the statement after the branching + val thenExit = thenCompound.end.nextEOGEdges[0] + assertFalse(thenExit.getProperty(Properties.UNREACHABLE) as Boolean) + + // Check if the else-branch is set as unreachable including all the edges until reaching the + // print + val elseDecl = ifStatement.nextEOGEdges[1] + assertTrue(elseDecl.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, elseDecl.end.nextEOGEdges.size) + // The "--" + val decOp = elseDecl.end.nextEOGEdges[0] + assertTrue(decOp.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, decOp.end.nextEOGEdges.size) + // The compoundStmt + val elseCompound = decOp.end.nextEOGEdges[0] + assertTrue(elseCompound.getProperty(Properties.UNREACHABLE) as Boolean) + assertEquals(1, elseCompound.end.nextEOGEdges.size) + // There's the outgoing EOG edge to the statement after the branching + val elseExit = elseCompound.end.nextEOGEdges[0] + assertTrue(elseExit.getProperty(Properties.UNREACHABLE) as Boolean) + + // After the branching, it's reachable again. Check that we found the merge node and that we + // continue with reachable edges. + assertEquals(thenExit.end, elseExit.end) + val mergeNode = thenExit.end + assertEquals(1, mergeNode.nextEOGEdges.size) + assertFalse(mergeNode.nextEOGEdges[0].getProperty(Properties.UNREACHABLE) as Boolean) } @Test diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index e2074b4101..251db8d75a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -26,7 +26,7 @@ package de.fraunhofer.aisec.cpg.helpers import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.declarations.Declaration +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import java.util.IdentityHashMap /** @@ -62,11 +62,11 @@ class PowersetLattice(override val elements: Set) : Lattice>(ele } /** - * Stores the current state. I.e., it maps a [Node] to a [Lattice]. It provides some useful + * Stores the current state. I.e., it maps a [V] [Node] to a [Lattice]. It provides some useful * functions e.g. to check if the mapping has to be updated (e.g. because there are new nodes or * because a new lattice is bigger than the old one). */ -open class State : IdentityHashMap>() { +open class State : IdentityHashMap>() { /** * It updates this state by adding all new nodes in [other] to `this` and by computing the least @@ -75,7 +75,7 @@ open class State : IdentityHashMap>() { * Returns this and a flag which states if there was any update necessary (or if `this` is equal * before and after running the method). */ - open fun lub(other: State): Pair, Boolean> { + open fun lub(other: State): Pair, Boolean> { var update = false for ((node, newLattice) in other) { update = push(node, newLattice) || update @@ -88,7 +88,7 @@ open class State : IdentityHashMap>() { * `this` and if the lattice of a node in [other] "is bigger" than the respective lattice in * `this`. It does not modify anything. */ - open fun needsUpdate(other: State): Boolean { + open fun needsUpdate(other: State): Boolean { var update = false for ((node, newLattice) in other) { update = update || node !in this || newLattice > this[node]!! @@ -97,8 +97,8 @@ open class State : IdentityHashMap>() { } /** Deep copies this object. */ - open fun duplicate(): State { - val clone = State() + open fun duplicate(): State { + val clone = State() for ((key, value) in this) { clone[key] = value.duplicate() } @@ -110,7 +110,7 @@ open class State : IdentityHashMap>() { * does not exist in this state yet. If it already exists, it computes the least upper bound of * [newLattice] and the current one for [newNode]. It returns if the state has changed. */ - open fun push(newNode: Node, newLattice: Lattice?): Boolean { + open fun push(newNode: K, newLattice: Lattice?): Boolean { if (newLattice == null) { return false } @@ -132,15 +132,15 @@ open class State : IdentityHashMap>() { * A worklist. Essentially, it stores mappings of nodes to the states which are available there and * determines which nodes have to be analyzed. */ -class Worklist() { +class Worklist() { /** A mapping of nodes to the state which is currently available there. */ - var globalState: MutableMap> = mutableMapOf() + var globalState: MutableMap> = mutableMapOf() private set /** A list of all nodes which have already been visited. */ - private val alreadySeen = mutableListOf() + private val alreadySeen = mutableListOf() - constructor(globalState: MutableMap> = mutableMapOf()) : this() { + constructor(globalState: MutableMap> = mutableMapOf()) : this() { this.globalState = globalState } @@ -148,13 +148,13 @@ class Worklist() { * The actual worklist, i.e., elements which still have to be analyzed and the state which * should be considered there. */ - private val nodeOrder: MutableList>> = mutableListOf() + private val nodeOrder: MutableList>> = mutableListOf() /** * Adds [newNode] and the [state] to the [globalState] (i.e., computes the [State.lub] of the * current state there and [state]). Returns true if there was an update. */ - fun update(newNode: Node, state: State): Boolean { + fun update(newNode: K, state: State): Boolean { val (newGlobalState, update) = globalState[newNode]?.lub(state) ?: Pair(state, true) if (update) { globalState[newNode] = newGlobalState @@ -167,7 +167,7 @@ class Worklist() { * the node. Returns `true` if there was a change which means that the node has to be analyzed. * If it returns `false`, the [newNode] wasn't added to the worklist as the state didn't change. */ - fun push(newNode: Node, state: State): Boolean { + fun push(newNode: K, state: State): Boolean { val currentEntry = nodeOrder.find { it.first == newNode } val update: Boolean val newEntry = @@ -192,17 +192,17 @@ class Worklist() { fun isEmpty() = nodeOrder.isEmpty() /** Removes a [Node] from the worklist and returns the [Node] together with its [State] */ - fun pop(): Pair> { + fun pop(): Pair> { val node = nodeOrder.removeFirst() alreadySeen.add(node.first) return node } /** Checks if [currentNode] has already been visited before. */ - fun hasAlreadySeen(currentNode: Node) = currentNode in alreadySeen + fun hasAlreadySeen(currentNode: K) = currentNode in alreadySeen /** Computes the meet over paths for all the states in [globalState]. */ - fun mop(): State { + fun mop(): State { val firstKey = globalState.keys.firstOrNull() val state = globalState[firstKey] for ((_, v) in globalState) { @@ -228,11 +228,11 @@ class EOGWorklist { * not every transition in the EOG will really lead to an update of the current state depending * on the analysis. */ - fun iterateEOG( - startNode: Node, - startState: State, - transformation: (Node, State, Worklist) -> Pair, Boolean> - ): State { + inline fun iterateEOG( + startNode: K, + startState: State, + transformation: (K, State, Worklist) -> Pair, Boolean> + ): State { val worklist = Worklist(mutableMapOf(Pair(startNode, startState))) worklist.push(startNode, startState) @@ -241,55 +241,35 @@ class EOGWorklist { val (newState, expectedUpdate) = transformation(nextNode, state.duplicate(), worklist) if (worklist.update(nextNode, newState) || !expectedUpdate) { - nextNode.nextEOG.forEach { worklist.push(it, newState.duplicate()) } + nextNode.nextEOG.forEach { if (it is K) worklist.push(it, newState.duplicate()) } } } return worklist.mop() } -} - -/** - * A state which actually holds a state for all nodes, one only for declarations and one for - * ReturnStatements. - */ -class DFGPassState( - /** A mapping of a [Node] to its [Lattice]. */ - var generalState: State = State(), - /** A mapping of [Declaration] to its [Lattice]. */ - var declarationsState: State = State(), - /** The [returnStatements] which are reachable. */ - var returnStatements: State = State() -) : State() { - override fun duplicate(): DFGPassState { - return DFGPassState(generalState.duplicate(), declarationsState.duplicate()) - } - override fun lub(other: State): Pair, Boolean> { - return if (other is DFGPassState) { - val (_, generalUpdate) = generalState.lub(other.generalState) - val (_, declUpdate) = declarationsState.lub(other.declarationsState) - Pair(this, generalUpdate || declUpdate) - } else { - val (_, generalUpdate) = generalState.lub(other) - Pair(this, generalUpdate) + inline fun , V> iterateEOG( + startEdges: List, + startState: State, + transformation: + (PropertyEdge, State, Worklist) -> Pair, Boolean> + ): State { + val globalState = mutableMapOf>() + for (startEdge in startEdges) { + globalState[startEdge] = startState } - } + val worklist = Worklist(globalState) + startEdges.forEach { worklist.push(it, startState) } - override fun needsUpdate(other: State): Boolean { - return if (other is DFGPassState) { - generalState.needsUpdate(other.generalState) || - declarationsState.needsUpdate(other.declarationsState) - } else { - generalState.needsUpdate(other) - } - } - - override fun push(newNode: Node, newLattice: Lattice?): Boolean { - return generalState.push(newNode, newLattice) - } + while (worklist.isNotEmpty()) { + val (nextEdge, state) = worklist.pop() - /** Pushes the [newNode] and its [newLattice] to the [declarationsState]. */ - fun pushToDeclarationsState(newNode: Declaration, newLattice: Lattice?): Boolean { - return declarationsState.push(newNode, newLattice) + val (newState, expectedUpdate) = transformation(nextEdge, state.duplicate(), worklist) + if (worklist.update(nextEdge, newState) || !expectedUpdate) { + nextEdge.end.nextEOGEdges.forEach { + if (it is K) worklist.push(it, newState.duplicate()) + } + } + } + return worklist.mop() } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index a4882f9019..968d1c901a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -77,7 +77,7 @@ class ControlFlowSensitiveDFGPass : Pass() { } ) - for ((key, value) in (finalState as DFGPassState).generalState) { + for ((key, value) in finalState.generalState) { key.addAllPrevDFG( value.elements.filterNot { it is VariableDeclaration && key == it } ) @@ -109,9 +109,9 @@ class ControlFlowSensitiveDFGPass : Pass() { @JvmStatic fun transfer( currentNode: Node, - state: State>, - worklist: Worklist> - ): Pair>, Boolean> { + state: State>, + worklist: Worklist> + ): Pair>, Boolean> { // We will set this if we write to a variable val writtenDecl: Declaration? @@ -291,3 +291,49 @@ class ControlFlowSensitiveDFGPass : Pass() { lastStatement.removeNextDFG(node) } } + +/** + * A state which actually holds a state for all nodes, one only for declarations and one for + * ReturnStatements. + */ +class DFGPassState( + /** A mapping of a [Node] to its [Lattice]. */ + var generalState: State = State(), + /** A mapping of [Declaration] to its [Lattice]. */ + var declarationsState: State = State(), + /** The [returnStatements] which are reachable. */ + var returnStatements: State = State() +) : State() { + override fun duplicate(): DFGPassState { + return DFGPassState(generalState.duplicate(), declarationsState.duplicate()) + } + + override fun lub(other: State): Pair, Boolean> { + return if (other is DFGPassState) { + val (_, generalUpdate) = generalState.lub(other.generalState) + val (_, declUpdate) = declarationsState.lub(other.declarationsState) + Pair(this, generalUpdate || declUpdate) + } else { + val (_, generalUpdate) = generalState.lub(other) + Pair(this, generalUpdate) + } + } + + override fun needsUpdate(other: State): Boolean { + return if (other is DFGPassState) { + generalState.needsUpdate(other.generalState) || + declarationsState.needsUpdate(other.declarationsState) + } else { + generalState.needsUpdate(other) + } + } + + override fun push(newNode: Node, newLattice: Lattice?): Boolean { + return generalState.push(newNode, newLattice) + } + + /** Pushes the [newNode] and its [newLattice] to the [declarationsState]. */ + fun pushToDeclarationsState(newNode: Declaration, newLattice: Lattice?): Boolean { + return declarationsState.push(newNode, newLattice) + } +} From d2dc925bc78074143829642ab18203b6c9074fb4 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 21 Mar 2023 16:14:34 +0100 Subject: [PATCH 04/53] Handle AssignExpressions in ControlFlowSensitiveDFGPass --- .../aisec/cpg/graph/SplitsControlFlow.kt | 2 +- .../aisec/cpg/graph/statements/CatchClause.kt | 2 +- .../cpg/graph/statements/ForEachStatement.kt | 2 +- .../cpg/graph/statements/ForStatement.kt | 2 +- .../aisec/cpg/graph/statements/IfStatement.kt | 2 +- .../cpg/graph/statements/SwitchStatement.kt | 2 +- .../cpg/graph/statements/WhileStatement.kt | 2 +- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 24 +++++++++++++++---- .../cpg/passes/EvaluationOrderGraphPass.kt | 2 +- 9 files changed, 28 insertions(+), 12 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt index 52039825f6..bafb8dda5d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt @@ -34,5 +34,5 @@ interface SplitsControlFlow { * The nodes which can be conditionally visited depending on [splittingNode]. It does only * contain the "next hop", not the child nodes of the subtree. */ - val affectedNodes: MutableList + val dominatedNodes: MutableList } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt index 0bca30731b..b6c33be268 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt @@ -42,7 +42,7 @@ class CatchClause : Statement(), SplitsControlFlow { get() = parameter @PopulatedByPass(EvaluationOrderGraphPass::class) - override val affectedNodes = mutableListOf() + override val dominatedNodes = mutableListOf() override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index 5ceb4a288b..240d8cd12a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -58,7 +58,7 @@ class ForEachStatement : Statement(), SplitsControlFlow { get() = iterable @PopulatedByPass(EvaluationOrderGraphPass::class) - override val affectedNodes = mutableListOf() + override val dominatedNodes = mutableListOf() override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt index 6527743171..9b76af47b3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt @@ -49,7 +49,7 @@ class ForStatement : Statement(), SplitsControlFlow { get() = condition ?: conditionDeclaration @PopulatedByPass(EvaluationOrderGraphPass::class) - override val affectedNodes = mutableListOf() + override val dominatedNodes = mutableListOf() override fun equals(other: Any?): Boolean { if (this === other) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt index b0c8d6a189..906c732209 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt @@ -51,7 +51,7 @@ class IfStatement : Statement(), SplitsControlFlow, ArgumentHolder { get() = condition ?: conditionDeclaration @PopulatedByPass(EvaluationOrderGraphPass::class) - override val affectedNodes = mutableListOf() + override val dominatedNodes = mutableListOf() /** C++ constexpr construct. */ var isConstExpression = false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt index fca5b1d9b6..d44720f160 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt @@ -59,7 +59,7 @@ class SwitchStatement : Statement(), SplitsControlFlow { get() = selector @PopulatedByPass(EvaluationOrderGraphPass::class) - override val affectedNodes = mutableListOf() + override val dominatedNodes = mutableListOf() override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt index 92225c036f..cb0704f5aa 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt @@ -53,7 +53,7 @@ class WhileStatement : Statement(), SplitsControlFlow { get() = condition ?: conditionDeclaration @PopulatedByPass(EvaluationOrderGraphPass::class) - override val affectedNodes = mutableListOf() + override val dominatedNodes = mutableListOf() override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 968d1c901a..4a9748c0c5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -32,10 +32,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.statements.* -import de.fraunhofer.aisec.cpg.graph.statements.expressions.BinaryOperator -import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.graph.statements.expressions.UnaryOperator +import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.helpers.* import de.fraunhofer.aisec.cpg.passes.order.DependsOn @@ -131,6 +128,25 @@ class ControlFlowSensitiveDFGPass : Pass() { currentNode, PowersetLattice(setOf(currentNode)) ) + } else if (currentNode is AssignExpression) { + expectedUpdate = true + // It's an assignment which can have one or multiple things on the lhs and on the + // rhs. The lhs could be a declaration or a reference (or multiple of these things). + // The rhs can be anything. The rhs flows to the respective lhs. To identify the + // correct mapping, we use the "assignments" property which already searches for us. + currentNode.assignments.forEach { assignment -> + // The rhs flows to the lhs + (assignment.target as? Node)?.let { + state.push(it, PowersetLattice(setOf(assignment.value))) + } + // This was the last write to the respective declaration. + (assignment.target as? Declaration + ?: (assignment.target as? DeclaredReferenceExpression)?.refersTo) + ?.let { + doubleState.declarationsState[it] = + PowersetLattice(setOf(assignment.target as Node)) + } + } } else if (isIncOrDec(currentNode)) { expectedUpdate = true // Increment or decrement => Add the prevWrite of the input to the input. After the diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index ed681e30cb..fcd6dc905b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -892,7 +892,7 @@ open class EvaluationOrderGraphPass : Pass() { } else { openBranchNodes.addAll(openConditionEOGs) } - node.affectedNodes.addAll(node.nextEOG) + node.dominatedNodes.addAll(node.nextEOG) scopeManager.leaveScope(node) setCurrentEOGs(openBranchNodes) } From 56c8a5524ef569cb572f14fa4d0b125232e596b7 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 21 Mar 2023 16:26:55 +0100 Subject: [PATCH 05/53] Mark statements which should be irrelevant because the same edges are set in the normal DFGPass --- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 4a9748c0c5..258433b652 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -138,7 +138,8 @@ class ControlFlowSensitiveDFGPass : Pass() { // The rhs flows to the lhs (assignment.target as? Node)?.let { state.push(it, PowersetLattice(setOf(assignment.value))) - } + } // TODO: This should be irrelevant + // This was the last write to the respective declaration. (assignment.target as? Declaration ?: (assignment.target as? DeclaredReferenceExpression)?.refersTo) @@ -165,11 +166,12 @@ class ControlFlowSensitiveDFGPass : Pass() { state.push( (currentNode as BinaryOperator).lhs, PowersetLattice(setOf(currentNode.rhs)) - ) + ) // TODO: This should be irrelevant // Only the lhs is the last write statement here and the variable which is written // to. - writtenDecl = (currentNode.lhs as DeclaredReferenceExpression).refersTo + writtenDecl = + ((currentNode as BinaryOperator).lhs as DeclaredReferenceExpression).refersTo if (writtenDecl != null) { doubleState.declarationsState[writtenDecl] = @@ -187,13 +189,23 @@ class ControlFlowSensitiveDFGPass : Pass() { if (writtenDecl != null) { // Data flows from the last writes to the lhs variable to this node state.push(currentNode.lhs, doubleState.declarationsState[writtenDecl]) - state.push(currentNode, PowersetLattice(setOf(currentNode.lhs))) + + state.push( + currentNode, + PowersetLattice(setOf(currentNode.lhs)) + ) // TODO: This should be irrelevant // Data flows from whatever is the rhs to this node - state.push(currentNode, PowersetLattice(setOf(currentNode.rhs))) + state.push( + currentNode, + PowersetLattice(setOf(currentNode.rhs)) + ) // TODO: This should be irrelevant // TODO: Similar to the ++ case: Should the DFG edge go back to the reference? // If it shouldn't, remove the following statement: - state.push(currentNode.lhs, PowersetLattice(setOf(currentNode))) + state.push( + currentNode.lhs, + PowersetLattice(setOf(currentNode)) + ) // TODO: This should be irrelevant // The whole current node is the place of the last update, not (only) the lhs! doubleState.declarationsState[writtenDecl] = From f1cd62b10fcad440eec695f14442ecbd51e52538 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Thu, 23 Mar 2023 09:52:57 +0100 Subject: [PATCH 06/53] Remove setting unnecessary (already existing) edges from CFSensitiveDFGPass --- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 258433b652..fe8aa4bf97 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -135,11 +135,6 @@ class ControlFlowSensitiveDFGPass : Pass() { // The rhs can be anything. The rhs flows to the respective lhs. To identify the // correct mapping, we use the "assignments" property which already searches for us. currentNode.assignments.forEach { assignment -> - // The rhs flows to the lhs - (assignment.target as? Node)?.let { - state.push(it, PowersetLattice(setOf(assignment.value))) - } // TODO: This should be irrelevant - // This was the last write to the respective declaration. (assignment.target as? Declaration ?: (assignment.target as? DeclaredReferenceExpression)?.refersTo) @@ -162,11 +157,6 @@ class ControlFlowSensitiveDFGPass : Pass() { } } else if (isSimpleAssignment(currentNode)) { expectedUpdate = true - // We write to the target => the rhs flows to the lhs - state.push( - (currentNode as BinaryOperator).lhs, - PowersetLattice(setOf(currentNode.rhs)) - ) // TODO: This should be irrelevant // Only the lhs is the last write statement here and the variable which is written // to. @@ -190,23 +180,6 @@ class ControlFlowSensitiveDFGPass : Pass() { // Data flows from the last writes to the lhs variable to this node state.push(currentNode.lhs, doubleState.declarationsState[writtenDecl]) - state.push( - currentNode, - PowersetLattice(setOf(currentNode.lhs)) - ) // TODO: This should be irrelevant - // Data flows from whatever is the rhs to this node - state.push( - currentNode, - PowersetLattice(setOf(currentNode.rhs)) - ) // TODO: This should be irrelevant - - // TODO: Similar to the ++ case: Should the DFG edge go back to the reference? - // If it shouldn't, remove the following statement: - state.push( - currentNode.lhs, - PowersetLattice(setOf(currentNode)) - ) // TODO: This should be irrelevant - // The whole current node is the place of the last update, not (only) the lhs! doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(currentNode.lhs)) From 3229f549c53f37714e23aa51985f5d9fd3d60033 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 27 Mar 2023 14:08:42 +0200 Subject: [PATCH 07/53] ConditionalExpression splits control flow --- .../statements/expressions/ConditionalExpression.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index bdac419afd..2bae426418 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -25,10 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.HasType -import de.fraunhofer.aisec.cpg.graph.TypeManager -import de.fraunhofer.aisec.cpg.graph.newUnknownType +import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.types.Type import java.util.ArrayList import java.util.Objects @@ -38,7 +35,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * Represents an expression containing a ternary operator: `var x = condition ? valueIfTrue : * valueIfFalse`; */ -class ConditionalExpression : Expression(), HasType.TypeListener { +class ConditionalExpression : Expression(), HasType.TypeListener, SplitsControlFlow { @AST var condition: Expression = ProblemExpression("could not parse condition expression") @AST @@ -96,6 +93,11 @@ class ConditionalExpression : Expression(), HasType.TypeListener { .build() } + override val splittingNode: Node + get() = condition + override val dominatedNodes: MutableList + get() = listOfNotNull(thenExpr, elseExpr).toMutableList() + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ConditionalExpression) return false From f5e634240d6bf5df73311926684298dd43b11934 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 27 Mar 2023 15:00:52 +0200 Subject: [PATCH 08/53] Fix issue 1141 --- .../cpg/frontends/cpp/ExpressionHandler.kt | 1 + .../de/fraunhofer/aisec/cpg/GraphExamples.kt | 28 +++++++++++++++++++ .../aisec/cpg/enhancements/DFGTest.kt | 26 +++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt index b9716032be..1158219aa6 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/cpp/ExpressionHandler.kt @@ -560,6 +560,7 @@ class ExpressionHandler(lang: CXXLanguageFrontend) : edge.addProperty(Properties.INDEX, expression.initializerEdges.size) expression.initializerEdges.add(edge) + expression.addPrevDFG(it) } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt index a91e4b77c4..76c3ab70ad 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/GraphExamples.kt @@ -28,10 +28,38 @@ package de.fraunhofer.aisec.cpg import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.newInitializerListExpression import de.fraunhofer.aisec.cpg.graph.newVariableDeclaration class GraphExamples { companion object { + fun getInitializerListExprDFG( + config: TranslationConfiguration = + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage(".")) + .build() + ) = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult(config) { + translationUnit("initializerListExprDFG.cpp") { + function("foo", t("int")) { body { returnStmt { literal(0, t("int")) } } } + function("main", t("int")) { + body { + declare { + variable("i", t("int")) { + val initList = newInitializerListExpression() + initList.initializers = listOf(call("foo")) + initializer = initList + } + } + returnStmt { ref("i") } + } + } + } + } + } + fun getVisitorTest( config: TranslationConfiguration = TranslationConfiguration.builder() diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt index 5b261cf7cb..75d8345936 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/enhancements/DFGTest.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.enhancements +import de.fraunhofer.aisec.cpg.GraphExamples import de.fraunhofer.aisec.cpg.TestUtils.analyze import de.fraunhofer.aisec.cpg.TestUtils.findByUniqueName import de.fraunhofer.aisec.cpg.graph.* @@ -35,6 +36,31 @@ import kotlin.test.* internal class DFGTest { // Test DFG // Test ControlFlowSensitiveDFGPass + @Test + fun testInitializerListExpression() { + val result = GraphExamples.getInitializerListExprDFG() + val variable = result.variables["i"] + assertNotNull(variable) + assertEquals(1, variable.prevDFG.size) + val initializer = variable.prevDFG.first() + assertEquals(1, initializer.prevDFG.size) + } + @Test + fun testInitializerListExpressionCode() { + val topLevel = Path.of("src", "test", "resources", "dfg") + val result = + analyze( + listOf(topLevel.resolve("initializerListExpression.cpp").toFile()), + topLevel, + true + ) + val variable = result.variables["i"] + assertNotNull(variable) + assertEquals(1, variable.prevDFG.size) + val initializer = variable.prevDFG.first() + assertEquals(1, initializer.prevDFG.size) + } + /** * To test assignments of different value in an expression that then has a joinPoint. a = a == b * ? b = 2: b = 3; From 3f58534fe0282f85bacbff55a9c1f3ce47905bb4 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 27 Mar 2023 15:02:59 +0200 Subject: [PATCH 09/53] Fix issue 1141 --- .../src/test/resources/dfg/initializerListExpression.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 cpg-core/src/test/resources/dfg/initializerListExpression.cpp diff --git a/cpg-core/src/test/resources/dfg/initializerListExpression.cpp b/cpg-core/src/test/resources/dfg/initializerListExpression.cpp new file mode 100644 index 0000000000..7fbc5e9cff --- /dev/null +++ b/cpg-core/src/test/resources/dfg/initializerListExpression.cpp @@ -0,0 +1,9 @@ +int foo() { + return 0; +} + +int main() { + int i{foo()}; + + return i; +} \ No newline at end of file From 9a57f2bc6d8c63f6c3545b5049e2cef2e0c132f8 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 28 Mar 2023 12:56:02 +0200 Subject: [PATCH 10/53] Consistency --- .../graph/statements/expressions/ConditionalExpression.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index 2bae426418..f607081027 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -25,8 +25,10 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.ArrayList import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder @@ -95,8 +97,8 @@ class ConditionalExpression : Expression(), HasType.TypeListener, SplitsControlF override val splittingNode: Node get() = condition - override val dominatedNodes: MutableList - get() = listOfNotNull(thenExpr, elseExpr).toMutableList() + @PopulatedByPass(EvaluationOrderGraphPass::class) + override val dominatedNodes: MutableList = mutableListOf() override fun equals(other: Any?): Boolean { if (this === other) return true From 68d97fec7ad2b6199d70663af3e8dc5eec2c0431 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 12 May 2023 08:37:38 +0200 Subject: [PATCH 11/53] Rename class and add fields --- ...{SplitsControlFlow.kt => BranchingNode.kt} | 10 +---- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 34 ++++++++++++++++- .../aisec/cpg/graph/statements/CatchClause.kt | 11 ++---- .../cpg/graph/statements/ForEachStatement.kt | 11 ++---- .../cpg/graph/statements/ForStatement.kt | 11 ++---- .../aisec/cpg/graph/statements/IfStatement.kt | 11 ++---- .../cpg/graph/statements/SwitchStatement.kt | 11 ++---- .../cpg/graph/statements/WhileStatement.kt | 11 ++---- .../statements/expressions/BinaryOperator.kt | 2 +- .../expressions/ConditionalExpression.kt | 8 +--- .../expressions/ShortCircuitOperator.kt | 34 +++++++++++++++++ .../cpg/passes/ControlDependenceGraphPass.kt | 38 +++++++++++++++++++ .../cpg/passes/EvaluationOrderGraphPass.kt | 1 - 13 files changed, 128 insertions(+), 65 deletions(-) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/{SplitsControlFlow.kt => BranchingNode.kt} (81%) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt similarity index 81% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt index bafb8dda5d..804e71e0d3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/SplitsControlFlow.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt @@ -26,13 +26,7 @@ package de.fraunhofer.aisec.cpg.graph /** A node triggering a conditional execution of other code. */ -interface SplitsControlFlow { +interface BranchingNode { /** The node which affects the next EOG edge. Typically, this is a condition or similar. */ - val splittingNode: Node? - - /** - * The nodes which can be conditionally visited depending on [splittingNode]. It does only - * contain the "next hop", not the child nodes of the subtree. - */ - val dominatedNodes: MutableList + val branchingDecision: Node? } 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 9c5ccebced..7693e23341 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 @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph import com.fasterxml.jackson.annotation.JsonBackReference +import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.frontends.Handler import de.fraunhofer.aisec.cpg.frontends.Language import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend @@ -36,12 +37,16 @@ import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.neo4j.LocationConverter import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter +import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass +import de.fraunhofer.aisec.cpg.passes.DFGPass +import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import de.fraunhofer.aisec.cpg.processing.IVisitable import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* @@ -103,14 +108,38 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider /** Incoming control flow edges. */ @Relationship(value = "EOG", direction = Relationship.Direction.INCOMING) + @PopulatedByPass(EvaluationOrderGraphPass::class) var prevEOGEdges: MutableList> = ArrayList() protected set /** outgoing control flow edges. */ @Relationship(value = "EOG", direction = Relationship.Direction.OUTGOING) + @PopulatedByPass(EvaluationOrderGraphPass::class) var nextEOGEdges: MutableList> = ArrayList() protected set + /** + * The nodes which are control-flow dominated, i.e., the children of the Control Dependence + * Graph (CDG). + */ + @PopulatedByPass(ControlDependenceGraphPass::class) + @Relationship(value = "CDG", direction = Relationship.Direction.OUTGOING) + var nextCDGEdges: MutableList> = ArrayList() + protected set + + var nextCDG by PropertyEdgeDelegate(Node::nextCDGEdges, true) + + /** + * The nodes which dominate this node via the control-flow, i.e., the parents of the Control + * Dependence Graph (CDG). + */ + @PopulatedByPass(ControlDependenceGraphPass::class) + @Relationship(value = "CDG", direction = Relationship.Direction.INCOMING) + var prevCDGEdges: MutableList> = ArrayList() + protected set + + var prevDG by PropertyEdgeDelegate(Node::prevCDGEdges, true) + /** * Virtual property to return a list of the node's children. Uses the [SubgraphWalker] to * retrieve the appropriate nodes. @@ -147,10 +176,13 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider this.nextEOGEdges = PropertyEdge.transformIntoOutgoingPropertyEdgeList(value, this) } + @PopulatedByPass(DFGPass::class) @Relationship(value = "DFG", direction = Relationship.Direction.INCOMING) var prevDFG: MutableSet = HashSet() - @Relationship(value = "DFG") var nextDFG: MutableSet = HashSet() + @PopulatedByPass(DFGPass::class) + @Relationship(value = "DFG") + var nextDFG: MutableSet = HashSet() var typedefs: MutableSet = HashSet() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt index b6c33be268..d1b99a1cc4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt @@ -25,25 +25,20 @@ */ package de.fraunhofer.aisec.cpg.graph.statements -import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.VariableDeclaration -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.Objects -class CatchClause : Statement(), SplitsControlFlow { +class CatchClause : Statement(), BranchingNode { @AST var parameter: VariableDeclaration? = null @AST var body: CompoundStatement? = null - override val splittingNode: Node? + override val branchingDecision: Node? get() = parameter - @PopulatedByPass(EvaluationOrderGraphPass::class) - override val dominatedNodes = mutableListOf() - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is CatchClause) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index 240d8cd12a..8710a5a1da 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -25,16 +25,14 @@ */ package de.fraunhofer.aisec.cpg.graph.statements -import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.AccessValues +import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.Objects -class ForEachStatement : Statement(), SplitsControlFlow { +class ForEachStatement : Statement(), BranchingNode { /** * This field contains the iteration variable of the loop. It can be either a new variable * declaration or a reference to an existing variable. @@ -54,12 +52,9 @@ class ForEachStatement : Statement(), SplitsControlFlow { /** This field contains the body of the loop. */ @AST var statement: Statement? = null - override val splittingNode: Node? + override val branchingDecision: Node? get() = iterable - @PopulatedByPass(EvaluationOrderGraphPass::class) - override val dominatedNodes = mutableListOf() - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ForEachStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt index 9b76af47b3..8a4dec9453 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt @@ -25,16 +25,14 @@ */ package de.fraunhofer.aisec.cpg.graph.statements -import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.* -class ForStatement : Statement(), SplitsControlFlow { +class ForStatement : Statement(), BranchingNode { @AST var statement: Statement? = null @AST var initializerStatement: Statement? = null @@ -45,12 +43,9 @@ class ForStatement : Statement(), SplitsControlFlow { @AST var iterationStatement: Statement? = null - override val splittingNode: Node? + override val branchingDecision: Node? get() = condition ?: conditionDeclaration - @PopulatedByPass(EvaluationOrderGraphPass::class) - override val dominatedNodes = mutableListOf() - override fun equals(other: Any?): Boolean { if (this === other) { return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt index 906c732209..d4596ba6c2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt @@ -25,19 +25,17 @@ */ package de.fraunhofer.aisec.cpg.graph.statements -import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST import de.fraunhofer.aisec.cpg.graph.ArgumentHolder +import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a condition control flow statement, usually indicating by `If`. */ -class IfStatement : Statement(), SplitsControlFlow, ArgumentHolder { +class IfStatement : Statement(), BranchingNode, ArgumentHolder { /** C++ initializer statement. */ @AST var initializerStatement: Statement? = null @@ -47,12 +45,9 @@ class IfStatement : Statement(), SplitsControlFlow, ArgumentHolder { /** The condition to be evaluated. */ @AST var condition: Expression? = null - override val splittingNode: Node? + override val branchingDecision: Node? get() = condition ?: conditionDeclaration - @PopulatedByPass(EvaluationOrderGraphPass::class) - override val dominatedNodes = mutableListOf() - /** C++ constexpr construct. */ var isConstExpression = false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt index d44720f160..1c035667f1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt @@ -25,13 +25,11 @@ */ package de.fraunhofer.aisec.cpg.graph.statements -import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.Objects /** @@ -39,7 +37,7 @@ import java.util.Objects * and default statements. Break statements break out of the switch and labeled breaks in JAva are * handled properly. */ -class SwitchStatement : Statement(), SplitsControlFlow { +class SwitchStatement : Statement(), BranchingNode { /** Selector that determines the case/default statement of the subsequent execution */ @AST var selector: Expression? = null @@ -55,12 +53,9 @@ class SwitchStatement : Statement(), SplitsControlFlow { */ @AST var statement: Statement? = null - override val splittingNode: Node? + override val branchingDecision: Node? get() = selector - @PopulatedByPass(EvaluationOrderGraphPass::class) - override val dominatedNodes = mutableListOf() - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is SwitchStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt index cb0704f5aa..9b35952bc9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt @@ -25,18 +25,16 @@ */ package de.fraunhofer.aisec.cpg.graph.statements -import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.AST +import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.SplitsControlFlow import de.fraunhofer.aisec.cpg.graph.declarations.Declaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.* import org.apache.commons.lang3.builder.ToStringBuilder /** Represents a conditional loop statement of the form: `while(...){...}`. */ -class WhileStatement : Statement(), SplitsControlFlow { +class WhileStatement : Statement(), BranchingNode { /** C++ allows defining a declaration instead of a pure logical expression as condition */ @AST var conditionDeclaration: Declaration? = null @@ -49,12 +47,9 @@ class WhileStatement : Statement(), SplitsControlFlow { */ @AST var statement: Statement? = null - override val splittingNode: Node? + override val branchingDecision: Node? get() = condition ?: conditionDeclaration - @PopulatedByPass(EvaluationOrderGraphPass::class) - override val dominatedNodes = mutableListOf() - override fun toString(): String { return ToStringBuilder(this, TO_STRING_STYLE) .appendSuper(super.toString()) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt index 8e9f9a078c..d73ba66f16 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/BinaryOperator.kt @@ -38,7 +38,7 @@ import org.neo4j.ogm.annotation.Transient * A binary operation expression, such as "a + b". It consists of a left hand expression (lhs), a * right hand expression (rhs) and an operatorCode. */ -class BinaryOperator : +open class BinaryOperator : Expression(), HasType.TypeListener, AssignmentHolder, HasBase, ArgumentHolder { /** The left-hand expression. */ @AST diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index f607081027..cf757bebd9 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -25,10 +25,8 @@ */ package de.fraunhofer.aisec.cpg.graph.statements.expressions -import de.fraunhofer.aisec.cpg.PopulatedByPass import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.types.Type -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass import java.util.ArrayList import java.util.Objects import org.apache.commons.lang3.builder.ToStringBuilder @@ -37,7 +35,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder * Represents an expression containing a ternary operator: `var x = condition ? valueIfTrue : * valueIfFalse`; */ -class ConditionalExpression : Expression(), HasType.TypeListener, SplitsControlFlow { +class ConditionalExpression : Expression(), HasType.TypeListener, BranchingNode { @AST var condition: Expression = ProblemExpression("could not parse condition expression") @AST @@ -95,10 +93,8 @@ class ConditionalExpression : Expression(), HasType.TypeListener, SplitsControlF .build() } - override val splittingNode: Node + override val branchingDecision: Node get() = condition - @PopulatedByPass(EvaluationOrderGraphPass::class) - override val dominatedNodes: MutableList = mutableListOf() override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt new file mode 100644 index 0000000000..5a72a80eb3 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023, 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.statements.expressions + +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node + +class ShortCircuitOperator : BinaryOperator(), BranchingNode { + override val branchingDecision: Node + get() = lhs +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt new file mode 100644 index 0000000000..cf37e0d45a --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, 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.passes + +import de.fraunhofer.aisec.cpg.TranslationResult + +class ControlDependenceGraphPass : Pass() { + override fun cleanup() { + TODO("Not yet implemented") + } + + override fun accept(t: TranslationResult) { + TODO("Not yet implemented") + } +} diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt index fcd6dc905b..86fd9e8cd3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/EvaluationOrderGraphPass.kt @@ -892,7 +892,6 @@ open class EvaluationOrderGraphPass : Pass() { } else { openBranchNodes.addAll(openConditionEOGs) } - node.dominatedNodes.addAll(node.nextEOG) scopeManager.leaveScope(node) setCurrentEOGs(openBranchNodes) } From 71fc6f710e2b742d5b95052ed7f1a0018983c130 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 16 May 2023 15:23:38 +0200 Subject: [PATCH 12/53] Generate CDG --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 2 +- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 5 + .../aisec/cpg/helpers/EOGWorklist.kt | 52 ++++-- .../cpg/passes/ControlDependenceGraphPass.kt | 173 +++++++++++++++++- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 2 +- .../passes/ControlDependenceGraphPassTest.kt | 49 +++++ .../aisec/cpg_vis_neo4j/Application.kt | 2 + 7 files changed, 268 insertions(+), 17 deletions(-) create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index d8456cc7c0..e8c5df11ed 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -80,7 +80,7 @@ class UnreachableEOGPass : Pass() { fun transfer( currentEdge: PropertyEdge, currentState: State, Reachability>, - currentWorklist: Worklist, Reachability> + currentWorklist: Worklist, PropertyEdge, Reachability> ): Pair, Reachability>, Boolean> { val currentNode = currentEdge.end if (currentNode is IfStatement) { 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 7693e23341..eca59c98a1 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 @@ -250,6 +250,11 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider prev.nextDFG.add(this) } + fun addPrevCDG(prev: Node) { + prevCDGEdges.add(PropertyEdge(prev, this)) + prev.nextCDGEdges.add(PropertyEdge(prev, this)) + } + fun addAllPrevDFG(prev: Collection) { prevDFG.addAll(prev) prev.forEach { it.nextDFG.add(this) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index 251db8d75a..5b6c5a0eb0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -62,9 +62,9 @@ class PowersetLattice(override val elements: Set) : Lattice>(ele } /** - * Stores the current state. I.e., it maps a [V] [Node] to a [Lattice]. It provides some useful - * functions e.g. to check if the mapping has to be updated (e.g. because there are new nodes or - * because a new lattice is bigger than the old one). + * Stores the current state. I.e., it maps [K] (e.g. a [Node] or [PropertyEdge]) to a [Lattice]. It + * provides some useful functions e.g. to check if the mapping has to be updated (e.g. because there + * are new nodes or because a new lattice is bigger than the old one). */ open class State : IdentityHashMap>() { @@ -132,15 +132,15 @@ open class State : IdentityHashMap>() { * A worklist. Essentially, it stores mappings of nodes to the states which are available there and * determines which nodes have to be analyzed. */ -class Worklist() { +class Worklist() { /** A mapping of nodes to the state which is currently available there. */ - var globalState: MutableMap> = mutableMapOf() + var globalState: MutableMap> = mutableMapOf() private set /** A list of all nodes which have already been visited. */ private val alreadySeen = mutableListOf() - constructor(globalState: MutableMap> = mutableMapOf()) : this() { + constructor(globalState: MutableMap> = mutableMapOf()) : this() { this.globalState = globalState } @@ -148,13 +148,13 @@ class Worklist() { * The actual worklist, i.e., elements which still have to be analyzed and the state which * should be considered there. */ - private val nodeOrder: MutableList>> = mutableListOf() + private val nodeOrder: MutableList>> = mutableListOf() /** * Adds [newNode] and the [state] to the [globalState] (i.e., computes the [State.lub] of the * current state there and [state]). Returns true if there was an update. */ - fun update(newNode: K, state: State): Boolean { + fun update(newNode: K, state: State): Boolean { val (newGlobalState, update) = globalState[newNode]?.lub(state) ?: Pair(state, true) if (update) { globalState[newNode] = newGlobalState @@ -167,7 +167,7 @@ class Worklist() { * the node. Returns `true` if there was a change which means that the node has to be analyzed. * If it returns `false`, the [newNode] wasn't added to the worklist as the state didn't change. */ - fun push(newNode: K, state: State): Boolean { + fun push(newNode: K, state: State): Boolean { val currentEntry = nodeOrder.find { it.first == newNode } val update: Boolean val newEntry = @@ -192,7 +192,7 @@ class Worklist() { fun isEmpty() = nodeOrder.isEmpty() /** Removes a [Node] from the worklist and returns the [Node] together with its [State] */ - fun pop(): Pair> { + fun pop(): Pair> { val node = nodeOrder.removeFirst() alreadySeen.add(node.first) return node @@ -202,7 +202,7 @@ class Worklist() { fun hasAlreadySeen(currentNode: K) = currentNode in alreadySeen /** Computes the meet over paths for all the states in [globalState]. */ - fun mop(): State { + fun mop(): State { val firstKey = globalState.keys.firstOrNull() val state = globalState[firstKey] for ((_, v) in globalState) { @@ -231,7 +231,7 @@ class EOGWorklist { inline fun iterateEOG( startNode: K, startState: State, - transformation: (K, State, Worklist) -> Pair, Boolean> + transformation: (K, State, Worklist) -> Pair, Boolean> ): State { val worklist = Worklist(mutableMapOf(Pair(startNode, startState))) worklist.push(startNode, startState) @@ -251,7 +251,7 @@ class EOGWorklist { startEdges: List, startState: State, transformation: - (PropertyEdge, State, Worklist) -> Pair, Boolean> + (PropertyEdge, State, Worklist) -> Pair, Boolean> ): State { val globalState = mutableMapOf>() for (startEdge in startEdges) { @@ -272,4 +272,30 @@ class EOGWorklist { } return worklist.mop() } + + inline fun , N : Node, V> iterateEOGEN( + startEdges: List, + startState: State, + transformation: + (PropertyEdge, State, Worklist) -> Pair, Boolean> + ): State { + val globalState = mutableMapOf>() + for (startEdge in startEdges) { + globalState[startEdge] = startState + } + val worklist = Worklist(globalState) + startEdges.forEach { worklist.push(it, startState) } + + while (worklist.isNotEmpty()) { + val (nextEdge, state) = worklist.pop() + + val (newState, expectedUpdate) = transformation(nextEdge, state.duplicate(), worklist) + if (worklist.update(nextEdge, newState) || !expectedUpdate) { + nextEdge.end.nextEOGEdges.forEach { + if (it is K) worklist.push(it, newState.duplicate()) + } + } + } + return worklist.mop() + } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index cf37e0d45a..978e538c9a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -26,13 +26,182 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.graph.BranchingNode +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.allChildren +import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration +import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge +import de.fraunhofer.aisec.cpg.graph.functions +import de.fraunhofer.aisec.cpg.graph.statements.IfStatement +import de.fraunhofer.aisec.cpg.helpers.EOGWorklist +import de.fraunhofer.aisec.cpg.helpers.Lattice +import de.fraunhofer.aisec.cpg.helpers.State +import de.fraunhofer.aisec.cpg.helpers.Worklist +import de.fraunhofer.aisec.cpg.passes.order.DependsOn +@DependsOn(EvaluationOrderGraphPass::class) class ControlDependenceGraphPass : Pass() { override fun cleanup() { - TODO("Not yet implemented") + // Nothing to do } override fun accept(t: TranslationResult) { - TODO("Not yet implemented") + t.functions.forEach(::handle) } + + private fun handle(functionDecl: FunctionDeclaration) { + // Maps nodes to their "cdg parent" (i.e. the dominator) and also has the information + // through which path it is reached. If all outgoing paths of the node's dominator result in + // the node, we use the dominator's state instead (i.e., we move the node one layer upwards) + val startState = PrevEOGState() + startState.push( + functionDecl, + PrevEOGLattice(mapOf(Pair(functionDecl, setOf(functionDecl)))) + ) + val finalState = + EOGWorklist().iterateEOGEN(functionDecl.nextEOGEdges, startState, ::handleEdge) + + // Collect the different branches for each branching node + val branchingNodeConditionals = + mapOf( + Pair(functionDecl, setOf(functionDecl)), + *functionDecl + .allChildren() + .map { Pair(it as Node, (it as Node).nextEOGEdges.map { it.end }) } + .toTypedArray() + ) + + // Collect the information, identify merge points, etc. This is not really efficient yet :( + for ((node, dominatorPaths) in finalState) { + val dominatorsList = + dominatorPaths.elements.entries + .map { (k, v) -> Pair(k, v.toMutableSet()) } + .toMutableList() + val finalDominators = mutableListOf>>() + while (dominatorsList.isNotEmpty()) { + val (k, v) = dominatorsList.removeFirst() + var update = false + if (k != functionDecl && v.containsAll(branchingNodeConditionals[k] ?: setOf())) { + // We are reachable from all the branches of branch. Add this parent to the + // worklist or update an existing entry. Also consider already existing entries + // in finalDominators list and update it (if necessary) + val newDominatorMap = finalState[k]?.elements + newDominatorMap?.forEach { (newK, newV) -> + if (dominatorsList.any { it.first == newK }) { + // Entry exists => update it + update = dominatorsList.first { it.first == newK }.second.addAll(newV) + } else if (finalDominators.any { it.first == newK }) { + // Entry in final dominators => Delete it and add it to the worklist + // (but only if something changed) + val entry = finalDominators.first { it.first == newK } + finalDominators.remove(entry) + update = entry.second.addAll(newV) + if (update) dominatorsList.add(entry) else finalDominators.add(entry) + } else { + // We don't have an entry yet => add a new one + update = dominatorsList.add(Pair(newK, newV.toMutableSet())) + } + } + } else { + // Node is not reachable from all branches => k dominates node. Add to + // finalDominators. + finalDominators.add(Pair(k, v)) + } + } + // We have all the dominators of this node and potentially traversed the graph + // "upwards". Add the CDG edges + finalDominators.forEach { (k, _) -> node.addPrevCDG(k) } + } + } + + companion object { + @JvmStatic + fun handleEdge( + currentEdge: PropertyEdge, + currentState: State>>, + currentWorklist: Worklist, Node, Map>> + ): Pair>>, Boolean> { + // Check if we start in a branching node and if this edge leads to the conditional + // branch. In this case, the next node will move "one layer downwards" in the CDG. + if (currentEdge.start is BranchingNode) { // && currentEdge.isConditionalBranch()) { + // We start in a branching node and end in one of the branches, so we have the + // following state: + // for the branching node "start", we have a path through "end". + currentState.push( + currentEdge.end, + PrevEOGLattice(mapOf(Pair(currentEdge.start, setOf(currentEdge.end)))) + ) + } else { + // We did not start in a branching node, so for the next node, we have the same path + // (last branching + first end node) as for the start node of this edge. + // If there is no state for the start node (most likely, this is the case for the + // first edge in a function), we generate a new state where we start in "start" end + // have "end" as the first node in the "branch". + val state = + PrevEOGLattice( + currentState[currentEdge.start]?.elements + ?: mapOf(Pair(currentEdge.start, setOf(currentEdge.end))) + ) + currentState.push(currentEdge.end, state) + } + return Pair(currentState, true) + } + } +} + +/** + * For all types I've seen so far, the "true" branch is executed conditionally. + * + * For if-statements, the BRANCH property is set to "false" for the "else" branch (which is also + * executed conditionally) and is not set in the code after an if-statement if there's no else + * branch (which is also always executed). For all other nodes, the "false" branch is the code after + * the loop or so (i.e., the unconditionally executed path). + * + * Note: This method does not account for return statements in the conditional part or endless loops + * where the other branch is actually also conditionally executed (or not). It should be easy to + * change this if we do not want this behavior (just remove the condition on the start node of the + * "false" branch). + */ +private fun PropertyEdge.isConditionalBranch(): Boolean { + return if (this.getProperty(Properties.BRANCH) == true) { + true + } else this.start is IfStatement && this.getProperty(Properties.BRANCH) == false } + +/** + * Implements the [Lattice] over a set of nodes and their set of "nextEOG" nodes which reach this + * node. + */ +class PrevEOGLattice(override val elements: Map>) : + Lattice>>(elements) { + override fun lub(other: Lattice>>): Lattice>> { + val newMap = other.elements.mapValues { (_, v) -> v.toMutableSet() }.toMutableMap() + for ((key, value) in this.elements) { + newMap.computeIfAbsent(key, ::mutableSetOf).addAll(value) + } + return PrevEOGLattice(newMap) + } + override fun duplicate() = PrevEOGLattice(this.elements.toMap()) + override fun compareTo(other: Lattice>>): Int { + return if ( + this.elements.keys.containsAll(other.elements.keys) && + this.elements.all { (k, v) -> v.containsAll(other.elements[k] ?: setOf()) } + ) { + if ( + this.elements.keys.size > other.elements.keys.size || + this.elements.any { (k, v) -> v.size > (other.elements[k] ?: setOf()).size } + ) + 1 + else 0 + } else { + -1 + } + } +} + +/** + * A state which actually holds a state for all [PropertyEdge]s, one only for declarations and one + * for ReturnStatements. + */ +class PrevEOGState : State>>() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index fe8aa4bf97..305d8b3d5b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -107,7 +107,7 @@ class ControlFlowSensitiveDFGPass : Pass() { fun transfer( currentNode: Node, state: State>, - worklist: Worklist> + worklist: Worklist> ): Pair>, Boolean> { // We will set this if we write to a variable val writtenDecl: Declaration? diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt new file mode 100644 index 0000000000..b44bfa0f33 --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023, 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.passes + +import de.fraunhofer.aisec.cpg.TestUtils +import de.fraunhofer.aisec.cpg.graph.records +import java.nio.file.Path +import kotlin.test.Test + +class ControlDependenceGraphPassTest { + companion object { + private val topLevel = Path.of("src", "test", "resources", "cfg") + } + @Test + fun testIfStatements() { + val result = + TestUtils.analyze( + listOf(Path.of(topLevel.toString(), "if.cpp").toFile()), + topLevel, + true + ) { + it.registerPass(ControlDependenceGraphPass()) + } + val records = result.records + } +} diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 5f27fec557..0ca182c69b 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -30,6 +30,7 @@ import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker +import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass import java.io.File import java.net.ConnectException import java.nio.file.Paths @@ -318,6 +319,7 @@ class Application : Callable { .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.llvm.LLVMIRLanguage") .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage") .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.typescript.TypeScriptLanguage") + .registerPass(ControlDependenceGraphPass()) .loadIncludes(loadIncludes) .addIncludesToGraph(loadIncludes) .debugParser(DEBUG_PARSER) From cb035c8789f0e9164e855c7438a779e958522790 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 17 May 2023 09:01:05 +0200 Subject: [PATCH 13/53] Fix problem with very strange EOG self reference of literals --- .../fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt | 3 ++- .../kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 19c1241553..d0348afb0e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -101,7 +101,8 @@ class ControlFlowSensitiveDFGPass : Pass() { node is ForEachStatement || node is DoStatement || node is GotoStatement || - node is ContinueStatement + node is ContinueStatement || + (node is Literal<*> && node in node.nextEOG) @JvmStatic fun transfer( diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 0ca182c69b..5f27fec557 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -30,7 +30,6 @@ import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker -import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass import java.io.File import java.net.ConnectException import java.nio.file.Paths @@ -319,7 +318,6 @@ class Application : Callable { .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.llvm.LLVMIRLanguage") .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage") .optionalLanguage("de.fraunhofer.aisec.cpg.frontends.typescript.TypeScriptLanguage") - .registerPass(ControlDependenceGraphPass()) .loadIncludes(loadIncludes) .addIncludesToGraph(loadIncludes) .debugParser(DEBUG_PARSER) From 011ab5d99adcdba1fe812462fbded4000c2bb76d Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 19 May 2023 11:42:59 +0200 Subject: [PATCH 14/53] Test if statements and cdg --- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 4 +- .../passes/ControlDependenceGraphPassTest.kt | 100 +++++++++++++++--- cpg-core/src/test/resources/cfg/if.cpp | 1 + .../aisec/cpg/frontends/TestLanguage.kt | 6 +- 4 files changed, 91 insertions(+), 20 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 3c1e03edd0..1413003202 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 @@ -140,7 +140,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider var prevCDGEdges: MutableList> = ArrayList() protected set - var prevDG by PropertyEdgeDelegate(Node::prevCDGEdges, true) + var prevCDG by PropertyEdgeDelegate(Node::prevCDGEdges, true) /** * Virtual property to return a list of the node's children. Uses the [SubgraphWalker] to @@ -255,7 +255,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider } fun addPrevCDG(prev: Node) { - prevCDGEdges.add(PropertyEdge(prev, this)) + prevCDGEdges.add(PropertyEdge(this, prev)) prev.nextCDGEdges.add(PropertyEdge(prev, this)) } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt index b44bfa0f33..24967c5c0f 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -25,25 +25,97 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.TestUtils -import de.fraunhofer.aisec.cpg.graph.records -import java.nio.file.Path +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.statements.CompoundStatement +import de.fraunhofer.aisec.cpg.graph.statements.expressions.Literal import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue class ControlDependenceGraphPassTest { - companion object { - private val topLevel = Path.of("src", "test", "resources", "cfg") - } + @Test fun testIfStatements() { - val result = - TestUtils.analyze( - listOf(Path.of(topLevel.toString(), "if.cpp").toFile()), - topLevel, - true - ) { - it.registerPass(ControlDependenceGraphPass()) + val result = getIfTest() + assertNotNull(result) + val main = result.functions["main"] + assertNotNull(main) + val if0 = (main.body as CompoundStatement).statements[1] + assertNotNull(if0) + assertEquals(1, if0.prevCDG.size) + assertTrue(main in if0.prevCDG) + + val assignment1 = + result.assignments.firstOrNull { 1 == (it.value as? Literal<*>)?.value }?.start + assertNotNull(assignment1) + assertEquals(1, assignment1.prevCDG.size) + assertTrue(if0 in assignment1.prevCDG) + + val print0 = + result.calls("printf").first { + "0\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(print0) + assertEquals(1, print0.prevCDG.size) + assertTrue(if0 in print0.prevCDG) + + val print1 = + result.calls("printf").first { + "1\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(print1) + assertEquals(1, print1.prevCDG.size) + assertTrue(main in print1.prevCDG) + + val print2 = + result.calls("printf").first { + "2\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(print2) + assertEquals(1, print2.prevCDG.size) + assertTrue(main in print2.prevCDG) + } + + companion object { + fun getIfTest() = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult( + TranslationConfiguration.builder() + .defaultPasses() + .registerLanguage(TestLanguage("::")) + .registerPass(ControlDependenceGraphPass()) + .build() + ) { + translationUnit("if.cpp") { + // The main method + function("main") { + body { + declare { variable("i", t("int")) { literal(0, t("int")) } } + ifStmt { + condition { ref("i") lt literal(1, t("int")) } + thenStmt { + ref("i") assign literal(1, t("int")) + call("printf") { addArgument(literal("0\n", t("string"))) } + } + } + call("printf") { addArgument(literal("1\n", t("string"))) } + ifStmt { + condition { ref("i") gt literal(0, t("int")) } + thenStmt { ref("i") assign literal(2, t("int")) } + elseStmt { ref("i") assign literal(3, t("int")) } + } + call("printf") { addArgument(literal("2\n", t("string"))) } + returnStmt { ref("i") } + } + } + } + } } - val records = result.records } } diff --git a/cpg-core/src/test/resources/cfg/if.cpp b/cpg-core/src/test/resources/cfg/if.cpp index 2add3aa2ff..5bc2501dec 100644 --- a/cpg-core/src/test/resources/cfg/if.cpp +++ b/cpg-core/src/test/resources/cfg/if.cpp @@ -11,4 +11,5 @@ int main(void){ else i = 1; printf("\n"); + return i; } \ No newline at end of file diff --git a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt index a618819497..23b677b555 100644 --- a/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt +++ b/cpg-core/src/testFixtures/kotlin/de/fraunhofer/aisec/cpg/frontends/TestLanguage.kt @@ -30,10 +30,7 @@ import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.statements.expressions.ProblemExpression -import de.fraunhofer.aisec.cpg.graph.types.FloatingPointType -import de.fraunhofer.aisec.cpg.graph.types.IntegerType -import de.fraunhofer.aisec.cpg.graph.types.NumericType -import de.fraunhofer.aisec.cpg.graph.types.Type +import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.io.File import java.util.function.Supplier @@ -60,6 +57,7 @@ open class TestLanguage(namespaceDelimiter: String = "::") : Language Date: Fri, 19 May 2023 15:51:00 +0200 Subject: [PATCH 15/53] Add test for foreach loop --- .../aisec/cpg/graph/builder/Fluent.kt | 31 ++++++++ .../cpg/graph/statements/ForEachStatement.kt | 35 +++++++-- .../cpg/passes/ControlDependenceGraphPass.kt | 22 +++++- .../passes/ControlDependenceGraphPassTest.kt | 77 ++++++++++++++++++- 4 files changed, 156 insertions(+), 9 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 94dc1c0ba3..a53c8d17bd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -460,6 +460,23 @@ fun LanguageFrontend.ifStmt(init: IfStatement.() -> Unit): IfStatement { return node } +/** + * Creates a new [ForEachStatement] in the Fluent Node DSL and adds it to the + * [StatementHolder.statements] of the nearest enclosing [StatementHolder]. The [init] block can be + * used to create further sub-nodes as well as configuring the created node itself. + */ +context(StatementHolder) + +fun LanguageFrontend.forEachStmt(init: ForEachStatement.() -> Unit): ForEachStatement { + val node = newForEachStatement() + + init(node) + + (this@StatementHolder) += node + + return node +} + /** * Creates a new [SwitchStatement] in the Fluent Node DSL and adds it to the * [StatementHolder.statements] of the nearest enclosing [StatementHolder]. The [init] block can be @@ -568,6 +585,20 @@ fun LanguageFrontend.elseIf(init: IfStatement.() -> Unit): IfStatement { */ context(WhileStatement) +fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { + val node = newCompoundStatement() + init(node) + statement = node + + return node +} +/** + * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the + * [WhileStatement.statement] of the nearest enclosing [WhileStatement]. The [init] block can be + * used to create further sub-nodes as well as configuring the created node itself. + */ +context(ForEachStatement) + fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { val node = newCompoundStatement() init(node) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index 8710a5a1da..65d0c37fa5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -25,14 +25,12 @@ */ package de.fraunhofer.aisec.cpg.graph.statements -import de.fraunhofer.aisec.cpg.graph.AST -import de.fraunhofer.aisec.cpg.graph.AccessValues -import de.fraunhofer.aisec.cpg.graph.BranchingNode -import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import java.util.Objects -class ForEachStatement : Statement(), BranchingNode { +class ForEachStatement : Statement(), BranchingNode, StatementHolder { /** * This field contains the iteration variable of the loop. It can be either a new variable * declaration or a reference to an existing variable. @@ -55,6 +53,33 @@ class ForEachStatement : Statement(), BranchingNode { override val branchingDecision: Node? get() = iterable + override var statementEdges: MutableList> + get() { + val statements = mutableListOf>() + variable?.let { statements.add(PropertyEdge(this, it)) } + iterable?.let { statements.add(PropertyEdge(this, it)) } + statement?.let { statements.add(PropertyEdge(this, it)) } + return statements + } + set(value) {} + + override fun addStatement(s: Statement) { + if (variable == null) { + variable = s + } else if (iterable == null) { + iterable = s + } else if (statement == null) { + statement = s + } else if (statement !is CompoundStatement) { + val newStmt = newCompoundStatement() + statement?.let { newStmt.addStatement(it) } + newStmt.addStatement(s) + statement = newStmt + } else { + (statement as? CompoundStatement)?.addStatement(s) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ForEachStatement) return false diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 978e538c9a..6ca092d89d 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -62,13 +62,31 @@ class ControlDependenceGraphPass : Pass() { val finalState = EOGWorklist().iterateEOGEN(functionDecl.nextEOGEdges, startState, ::handleEdge) - // Collect the different branches for each branching node + // For a branching node, we identify which path(s) have to be found to be in a "merging + // point". There are two options: 1) There's a path which is executed independent of the + // branch (e.g. this is the case for an-if statement without an else-branch). 2) this node + // can be reached from all conditional branches val branchingNodeConditionals = mapOf( Pair(functionDecl, setOf(functionDecl)), *functionDecl .allChildren() - .map { Pair(it as Node, (it as Node).nextEOGEdges.map { it.end }) } + .map { + Pair( + it as Node, + if ( + (it as? Node)?.nextEOGEdges?.any { !it.isConditionalBranch() } == + true + ) { + (it as? Node) + ?.nextEOGEdges + ?.filter { !it.isConditionalBranch() } + ?.map { it.end } + } else { + (it as Node).nextEOGEdges.map { it.end } + } + ) + } .toTypedArray() ) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt index 24967c5c0f..4de25fb1db 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -82,19 +82,56 @@ class ControlDependenceGraphPassTest { assertTrue(main in print2.prevCDG) } + @Test + fun testForEachLoop() { + val result = getForEachTest() + assertNotNull(result) + val main = result.functions["main"] + assertNotNull(main) + val forEachStmt = (main.body as CompoundStatement).statements[1] + assertNotNull(forEachStmt) + assertEquals(2, forEachStmt.prevCDG.size) + assertTrue(main in forEachStmt.prevCDG) + assertTrue( + forEachStmt in forEachStmt.prevCDG + ) // TODO: Is this really correct or should it be filtered out in the pass? + + val printInLoop = + result.calls("printf").first { + "loop: \${}\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(printInLoop) + assertEquals(1, printInLoop.prevCDG.size) + assertTrue(forEachStmt in printInLoop.prevCDG) + + val printAfterLoop = + result.calls("printf").first { + "1\n" == (it.arguments.firstOrNull() as? Literal<*>)?.value + } + assertNotNull(printAfterLoop) + assertEquals(2, printAfterLoop.prevCDG.size) + assertTrue(main in printAfterLoop.prevCDG) + assertTrue( + forEachStmt in printAfterLoop.prevCDG + ) // TODO: Is this really correct or should it be filtered out in the pass? + } + companion object { fun getIfTest() = TestLanguageFrontend(ScopeManager(), ".").build { translationResult( TranslationConfiguration.builder() - .defaultPasses() .registerLanguage(TestLanguage("::")) + .registerPass(TypeHierarchyResolver()) + .registerPass(VariableUsageResolver()) + .registerPass(CallResolver()) + .registerPass(EvaluationOrderGraphPass()) .registerPass(ControlDependenceGraphPass()) .build() ) { translationUnit("if.cpp") { // The main method - function("main") { + function("main", t("int")) { body { declare { variable("i", t("int")) { literal(0, t("int")) } } ifStmt { @@ -117,5 +154,41 @@ class ControlDependenceGraphPassTest { } } } + + fun getForEachTest() = + TestLanguageFrontend(ScopeManager(), ".").build { + translationResult( + TranslationConfiguration.builder() + .registerLanguage(TestLanguage("::")) + .registerPass(TypeHierarchyResolver()) + .registerPass(VariableUsageResolver()) + .registerPass(CallResolver()) + .registerPass(EvaluationOrderGraphPass()) + .registerPass(ControlDependenceGraphPass()) + .build() + ) { + translationUnit("forEach.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("i", t("int")) { literal(0, t("int")) } } + forEachStmt { + declare { variable("loopVar", t("string")) } + call("magicFunction") + loopBody { + call("printf") { + addArgument(literal("loop: \${}\n", t("string"))) + addArgument(ref("loopVar")) + } + } + } + call("printf") { addArgument(literal("1\n", t("string"))) } + + returnStmt { ref("i") } + } + } + } + } + } } } From 85602568002f664e19addf2124f7e03a25708e63 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 22 May 2023 09:31:37 +0200 Subject: [PATCH 16/53] Reduce sonar warnings --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 7 +- .../cpg/graph/statements/ForEachStatement.kt | 4 +- .../aisec/cpg/helpers/EOGWorklist.kt | 20 ++-- .../cpg/passes/ControlDependenceGraphPass.kt | 92 +++++++++++-------- .../passes/ControlDependenceGraphPassTest.kt | 10 +- 5 files changed, 73 insertions(+), 60 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index e8c5df11ed..dcd92f651d 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -174,10 +174,11 @@ class UnreachableEOGPass : Pass() { /** Implements the [Lattice] over reachability properties: TOP | REACHABLE | UNREACHABLE | BOTTOM */ class ReachabilityLattice(override val elements: Reachability) : Lattice(elements) { - override fun lub(other: Lattice) = - ReachabilityLattice(maxOf(this.elements, other.elements)) + override fun lub(other: Lattice?) = + ReachabilityLattice(maxOf(this.elements, other?.elements ?: Reachability.BOTTOM)) override fun duplicate() = ReachabilityLattice(this.elements) - override fun compareTo(other: Lattice) = this.elements.compareTo(other.elements) + override fun compareTo(other: Lattice?) = + this.elements.compareTo(other?.elements ?: Reachability.BOTTOM) } enum class Reachability { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index 65d0c37fa5..915ec802e2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -61,7 +61,9 @@ class ForEachStatement : Statement(), BranchingNode, StatementHolder { statement?.let { statements.add(PropertyEdge(this, it)) } return statements } - set(value) {} + set(value) { + // Nothing to do here + } override fun addStatement(s: Statement) { if (variable == null) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index 5b6c5a0eb0..a04b02d59a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -34,12 +34,12 @@ import java.util.IdentityHashMap * [T]. Implementations of this class have to implement the comparator, the least upper bound of two * lattices. */ -abstract class Lattice(open val elements: T) : Comparable> { +abstract class Lattice(open val elements: T) : Comparable?> { /** * Computes the least upper bound of this lattice and [other]. It returns a new object and does * not modify either of the objects. */ - abstract fun lub(other: Lattice): Lattice + abstract fun lub(other: Lattice?): Lattice /** Duplicates the object, i.e., makes a deep copy. */ abstract fun duplicate(): Lattice @@ -49,12 +49,12 @@ abstract class Lattice(open val elements: T) : Comparable> { * Implements the [Lattice] over a set of nodes. The lattice itself is constructed by the powerset. */ class PowersetLattice(override val elements: Set) : Lattice>(elements) { - override fun lub(other: Lattice>) = - PowersetLattice(other.elements.union(this.elements)) + override fun lub(other: Lattice>?) = + PowersetLattice((other?.elements ?: setOf()).union(this.elements)) override fun duplicate() = PowersetLattice(this.elements.toSet()) - override fun compareTo(other: Lattice>): Int { - return if (this.elements.containsAll(other.elements)) { - if (this.elements.size > other.elements.size) 1 else 0 + override fun compareTo(other: Lattice>?): Int { + return if (this.elements.containsAll(other?.elements ?: setOf())) { + if (this.elements.size > (other?.elements?.size ?: 0)) 1 else 0 } else { -1 } @@ -91,7 +91,7 @@ open class State : IdentityHashMap>() { open fun needsUpdate(other: State): Boolean { var update = false for ((node, newLattice) in other) { - update = update || node !in this || newLattice > this[node]!! + update = update || node !in this || newLattice > this[node] } return update } @@ -114,13 +114,13 @@ open class State : IdentityHashMap>() { if (newLattice == null) { return false } - if (newNode in this && this[newNode]!! >= newLattice) { + if (newNode in this && newLattice <= this[newNode]) { // newLattice is "smaller" than the currently stored one. We don't add it anything. return false } else if (newNode in this) { // newLattice is "bigger" than the currently stored one. We update it to the least // upper bound - this[newNode] = this[newNode]!!.lub(newLattice) + this[newNode] = newLattice.lub(this[newNode]) } else { this[newNode] = newLattice.duplicate() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 6ca092d89d..3d84ec234c 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -62,33 +62,7 @@ class ControlDependenceGraphPass : Pass() { val finalState = EOGWorklist().iterateEOGEN(functionDecl.nextEOGEdges, startState, ::handleEdge) - // For a branching node, we identify which path(s) have to be found to be in a "merging - // point". There are two options: 1) There's a path which is executed independent of the - // branch (e.g. this is the case for an-if statement without an else-branch). 2) this node - // can be reached from all conditional branches - val branchingNodeConditionals = - mapOf( - Pair(functionDecl, setOf(functionDecl)), - *functionDecl - .allChildren() - .map { - Pair( - it as Node, - if ( - (it as? Node)?.nextEOGEdges?.any { !it.isConditionalBranch() } == - true - ) { - (it as? Node) - ?.nextEOGEdges - ?.filter { !it.isConditionalBranch() } - ?.map { it.end } - } else { - (it as Node).nextEOGEdges.map { it.end } - } - ) - } - .toTypedArray() - ) + val branchingNodeConditionals = getBranchingNodeConditions(functionDecl) // Collect the information, identify merge points, etc. This is not really efficient yet :( for ((node, dominatorPaths) in finalState) { @@ -99,7 +73,6 @@ class ControlDependenceGraphPass : Pass() { val finalDominators = mutableListOf>>() while (dominatorsList.isNotEmpty()) { val (k, v) = dominatorsList.removeFirst() - var update = false if (k != functionDecl && v.containsAll(branchingNodeConditionals[k] ?: setOf())) { // We are reachable from all the branches of branch. Add this parent to the // worklist or update an existing entry. Also consider already existing entries @@ -108,17 +81,17 @@ class ControlDependenceGraphPass : Pass() { newDominatorMap?.forEach { (newK, newV) -> if (dominatorsList.any { it.first == newK }) { // Entry exists => update it - update = dominatorsList.first { it.first == newK }.second.addAll(newV) + dominatorsList.first { it.first == newK }.second.addAll(newV) } else if (finalDominators.any { it.first == newK }) { // Entry in final dominators => Delete it and add it to the worklist // (but only if something changed) val entry = finalDominators.first { it.first == newK } finalDominators.remove(entry) - update = entry.second.addAll(newV) + val update = entry.second.addAll(newV) if (update) dominatorsList.add(entry) else finalDominators.add(entry) } else { // We don't have an entry yet => add a new one - update = dominatorsList.add(Pair(newK, newV.toMutableSet())) + dominatorsList.add(Pair(newK, newV.toMutableSet())) } } } else { @@ -133,6 +106,46 @@ class ControlDependenceGraphPass : Pass() { } } + /* + * For a branching node, we identify which path(s) have to be found to be in a "merging point". + * There are two options: + * 1) There's a path which is executed independent of the branch (e.g. this is the case for an if-statement without an else-branch). + * 2) A node can be reached from all conditional branches. + * + * This method collects the merging points. It also includes the function declaration itself. + */ + private fun getBranchingNodeConditions(functionDecl: FunctionDeclaration) = + mapOf( + // For the function declaration, there's only the path through the function declaration + // itself. + Pair(functionDecl, setOf(functionDecl)), + *functionDecl + .allChildren() + .map { branchingNode -> + val mergingPoints = + if ( + (branchingNode as? Node)?.nextEOGEdges?.any { + !it.isConditionalBranch() + } == true + ) { + // There's an unconditional path (case 1), so when reaching this branch, + // we're done. Collect all (=1) unconditional branches. + (branchingNode as? Node) + ?.nextEOGEdges + ?.filter { !it.isConditionalBranch() } + ?.map { it.end } + ?.toSet() + } else { + // All branches are executed based on some condition (case 2), so we + // collect all these branches. + (branchingNode as Node).nextEOGEdges.map { it.end }.toSet() + } + // Map this branching node to its merging points + Pair(branchingNode as Node, mergingPoints) + } + .toTypedArray() + ) + companion object { @JvmStatic fun handleEdge( @@ -193,22 +206,25 @@ private fun PropertyEdge.isConditionalBranch(): Boolean { */ class PrevEOGLattice(override val elements: Map>) : Lattice>>(elements) { - override fun lub(other: Lattice>>): Lattice>> { - val newMap = other.elements.mapValues { (_, v) -> v.toMutableSet() }.toMutableMap() + override fun lub(other: Lattice>>?): Lattice>> { + val newMap = + (other?.elements ?: mapOf()).mapValues { (_, v) -> v.toMutableSet() }.toMutableMap() for ((key, value) in this.elements) { newMap.computeIfAbsent(key, ::mutableSetOf).addAll(value) } return PrevEOGLattice(newMap) } override fun duplicate() = PrevEOGLattice(this.elements.toMap()) - override fun compareTo(other: Lattice>>): Int { + override fun compareTo(other: Lattice>>?): Int { return if ( - this.elements.keys.containsAll(other.elements.keys) && - this.elements.all { (k, v) -> v.containsAll(other.elements[k] ?: setOf()) } + this.elements.keys.containsAll(other?.elements?.keys ?: setOf()) && + this.elements.all { (k, v) -> v.containsAll(other?.elements?.get(k) ?: setOf()) } ) { if ( - this.elements.keys.size > other.elements.keys.size || - this.elements.any { (k, v) -> v.size > (other.elements[k] ?: setOf()).size } + this.elements.keys.size > (other?.elements?.keys?.size ?: 0) || + this.elements.any { (k, v) -> + v.size > (other?.elements?.get(k) ?: setOf()).size + } ) 1 else 0 diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt index 4de25fb1db..8c2186e80c 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -122,10 +122,7 @@ class ControlDependenceGraphPassTest { translationResult( TranslationConfiguration.builder() .registerLanguage(TestLanguage("::")) - .registerPass(TypeHierarchyResolver()) - .registerPass(VariableUsageResolver()) - .registerPass(CallResolver()) - .registerPass(EvaluationOrderGraphPass()) + .defaultPasses() .registerPass(ControlDependenceGraphPass()) .build() ) { @@ -160,10 +157,7 @@ class ControlDependenceGraphPassTest { translationResult( TranslationConfiguration.builder() .registerLanguage(TestLanguage("::")) - .registerPass(TypeHierarchyResolver()) - .registerPass(VariableUsageResolver()) - .registerPass(CallResolver()) - .registerPass(EvaluationOrderGraphPass()) + .defaultPasses() .registerPass(ControlDependenceGraphPass()) .build() ) { From 1356f94a987624dccca1a591fc5deb1f9676c4e4 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 22 May 2023 13:41:02 +0200 Subject: [PATCH 17/53] Fix failing tests --- .../main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index a04b02d59a..5617da920e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -114,7 +114,7 @@ open class State : IdentityHashMap>() { if (newLattice == null) { return false } - if (newNode in this && newLattice <= this[newNode]) { + if (newNode in this && this[newNode]?.let { it >= newLattice } == true) { // newLattice is "smaller" than the currently stored one. We don't add it anything. return false } else if (newNode in this) { From ee8e2ed88bbcbea4d55767fd6414eec556ee3c32 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 22 May 2023 14:20:58 +0200 Subject: [PATCH 18/53] Use HasShortcircuitOperators to generate ShortcircuitOperator objects in Expression Builder --- .../aisec/cpg/frontends/LanguageTraits.kt | 3 +++ .../fraunhofer/aisec/cpg/graph/ExpressionBuilder.kt | 13 ++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index c1d962c284..b9f8532ba3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt @@ -206,4 +206,7 @@ interface HasShortCircuitOperators : LanguageTrait { val conjunctiveOperators: List // '||', 'or', 'v' val disjunctiveOperators: List + + val operatorCodes: Set + get() = conjunctiveOperators.union(disjunctiveOperators) } 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 ca734943b3..b8dea6106c 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 @@ -26,6 +26,7 @@ package de.fraunhofer.aisec.cpg.graph import de.fraunhofer.aisec.cpg.frontends.Handler +import de.fraunhofer.aisec.cpg.frontends.HasShortCircuitOperators import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node.Companion.EMPTY_NAME import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log @@ -68,7 +69,17 @@ fun MetadataProvider.newBinaryOperator( code: String? = null, rawNode: Any? = null ): BinaryOperator { - val node = BinaryOperator() + val node = + if ( + this is LanguageProvider && + (this.language as? HasShortCircuitOperators) + ?.operatorCodes + ?.contains(operatorCode) == true + ) { + ShortCircuitOperator() + } else { + BinaryOperator() + } node.applyMetadata(this, operatorCode, rawNode, code, true) node.operatorCode = operatorCode From 8f5e0c496b8694134c2661e40a5565d438e7ad26 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 23 May 2023 15:48:10 +0200 Subject: [PATCH 19/53] Little fix --- .../aisec/cpg/passes/ControlDependenceGraphPass.kt | 2 +- .../aisec/cpg/passes/ControlDependenceGraphPassTest.kt | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 3d84ec234c..dbccaade6e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -102,7 +102,7 @@ class ControlDependenceGraphPass : Pass() { } // We have all the dominators of this node and potentially traversed the graph // "upwards". Add the CDG edges - finalDominators.forEach { (k, _) -> node.addPrevCDG(k) } + finalDominators.filter { (k, _) -> k != node }.forEach { (k, _) -> node.addPrevCDG(k) } } } diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt index 8c2186e80c..711e78e815 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -90,11 +90,8 @@ class ControlDependenceGraphPassTest { assertNotNull(main) val forEachStmt = (main.body as CompoundStatement).statements[1] assertNotNull(forEachStmt) - assertEquals(2, forEachStmt.prevCDG.size) + assertEquals(1, forEachStmt.prevCDG.size) assertTrue(main in forEachStmt.prevCDG) - assertTrue( - forEachStmt in forEachStmt.prevCDG - ) // TODO: Is this really correct or should it be filtered out in the pass? val printInLoop = result.calls("printf").first { From 0edfa6e1e1becaa1e55dd3f1dac68b01ffa41f07 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 31 May 2023 15:48:44 +0200 Subject: [PATCH 20/53] Integrate review feedback --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 198 +++++---- .../aisec/cpg/frontends/LanguageTraits.kt | 15 +- .../aisec/cpg/graph/BranchingNode.kt | 2 +- .../aisec/cpg/graph/ExpressionBuilder.kt | 10 +- .../aisec/cpg/graph/statements/CatchClause.kt | 2 +- .../cpg/graph/statements/ForEachStatement.kt | 2 +- .../cpg/graph/statements/ForStatement.kt | 2 +- .../aisec/cpg/graph/statements/IfStatement.kt | 2 +- .../cpg/graph/statements/SwitchStatement.kt | 2 +- .../cpg/graph/statements/WhileStatement.kt | 2 +- .../expressions/ConditionalExpression.kt | 2 +- .../expressions/ShortCircuitOperator.kt | 2 +- .../cpg/passes/ControlDependenceGraphPass.kt | 89 +++-- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 376 +++++++++--------- 14 files changed, 380 insertions(+), 326 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index dcd92f651d..f8cb8c7f72 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -74,113 +74,137 @@ class UnreachableEOGPass : Pass() { } } } +} - companion object { - @JvmStatic - fun transfer( - currentEdge: PropertyEdge, - currentState: State, Reachability>, - currentWorklist: Worklist, PropertyEdge, Reachability> - ): Pair, Reachability>, Boolean> { - val currentNode = currentEdge.end - if (currentNode is IfStatement) { - handleIfStatement(currentEdge, currentNode, currentState) - } else if (currentNode is WhileStatement) { - handleWhileStatement(currentEdge, currentNode, currentState) - } else { - // For all other edges, we simply propagate the reachability property of the edge - // which made us come here. - currentNode.nextEOGEdges.forEach { - currentState.push(it, currentState[currentEdge]) - } - } - - return Pair(currentState, true) - } - - private fun handleIfStatement( - enteringEdge: PropertyEdge, - n: IfStatement, - state: State, Reachability> - ) { - val evalResult = ValueEvaluator().evaluate(n.condition) - - val (unreachableEdge, remainingEdges) = - if (evalResult is Boolean && evalResult == true) { - Pair( - n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }, - n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 } - ) - } else if (evalResult is Boolean && evalResult == false) { - Pair( - n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }, - n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 } - ) - } else { - Pair(null, n.nextEOGEdges) - } +/** + * This method is executed for each EOG edge which is in the worklist. [currentEdge] is the edge to + * process, [currentState] contains the state which was observed before arriving here. + * + * This method modifies the state for the next eog edge as follows: + * - If the next node in the eog is an [IfStatement], the condition is evaluated and if it is either + * always true or false, the else or then branch receives set to [Reachability.UNREACHABLE]. + * - If the next node in the eog is a [WhileStatement], the condition is evaluated and if it's + * always true or false, either the EOG edge to the loop body or out of the loop body is set to + * [Reachability.UNREACHABLE]. + * - For all other nodes, we simply propagate the state which led us here. + * + * Returns the updated state and true because we always expect an update of the state. + */ +fun transfer( + currentEdge: PropertyEdge, + currentState: State, Reachability>, + currentWorklist: Worklist, PropertyEdge, Reachability> +): Pair, Reachability>, Boolean> { + val currentNode = currentEdge.end + if (currentNode is IfStatement) { + handleIfStatement(currentEdge, currentNode, currentState) + } else if (currentNode is WhileStatement) { + handleWhileStatement(currentEdge, currentNode, currentState) + } else { + // For all other edges, we simply propagate the reachability property of the edge + // which made us come here. + currentNode.nextEOGEdges.forEach { currentState.push(it, currentState[currentEdge]) } + } - if (unreachableEdge != null) { - // This edge is definitely unreachable - state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE)) - } + return Pair(currentState, true) +} - // For all other edges, we simply propagate the reachability property of the edge which - // made us come here. - remainingEdges.forEach { state.push(it, state[enteringEdge]) } +/** + * Evaluates the condition of the [IfStatement] [n] (which is the end node of [enteringEdge]). If it + * is always true, then the else-branch receives the [state] [Reachability.UNREACHABLE]. If the + * condition is always false, then the then-branch receives the [state] [Reachability.UNREACHABLE]. + * All other cases simply copy the state which led us here. + */ +private fun handleIfStatement( + enteringEdge: PropertyEdge, + n: IfStatement, + state: State, Reachability> +) { + val evalResult = ValueEvaluator().evaluate(n.condition) + + val (unreachableEdge, remainingEdges) = + if (evalResult is Boolean && evalResult == true) { + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 } + ) + } else if (evalResult is Boolean && evalResult == false) { + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 } + ) + } else { + Pair(null, n.nextEOGEdges) } - private fun handleWhileStatement( - enteringEdge: PropertyEdge, - n: WhileStatement, - state: State, Reachability> - ) { - /* - * Note: It does not understand that code like - * x = true; while(x) {...; x = false;} - * makes the loop execute at least once. - * Apparently, the CPG does not offer the required functionality to - * differentiate between the first and subsequent evaluations of the - * condition. - */ - val evalResult = ValueEvaluator().evaluate(n.condition) - - val (unreachableEdge, remainingEdges) = - if (evalResult is Boolean && evalResult == true) { - Pair( - n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }, - n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 } - ) - } else if (evalResult is Boolean && evalResult == false) { - Pair( - n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }, - n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 } - ) - } else { - Pair(null, n.nextEOGEdges) - } + if (unreachableEdge != null) { + // This edge is definitely unreachable + state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE)) + } - if (unreachableEdge != null) { - // This edge is definitely unreachable - state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE)) - } + // For all other edges, we simply propagate the reachability property of the edge which + // made us come here. + remainingEdges.forEach { state.push(it, state[enteringEdge]) } +} - // For all other edges, we simply propagate the reachability property of the edge which - // made us come here. - remainingEdges.forEach { state.push(it, state[enteringEdge]) } +/** + * Evaluates the condition of the [WhileStatement] [n] (which is the end node of [enteringEdge]). If + * it is always true, then the edge to the code after the loop receives the [state] + * [Reachability.UNREACHABLE]. If the condition is always false, then the edge to the loop body + * receives the [state] [Reachability.UNREACHABLE]. All other cases simply copy the state which led + * us here. + */ +private fun handleWhileStatement( + enteringEdge: PropertyEdge, + n: WhileStatement, + state: State, Reachability> +) { + /* + * Note: It does not understand that code like + * x = true; while(x) {...; x = false;} + * makes the loop execute at least once. + * Apparently, the CPG does not offer the required functionality to + * differentiate between the first and subsequent evaluations of the + * condition. + */ + val evalResult = ValueEvaluator().evaluate(n.condition) + + val (unreachableEdge, remainingEdges) = + if (evalResult is Boolean && evalResult == true) { + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 1 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 1 } + ) + } else if (evalResult is Boolean && evalResult == false) { + Pair( + n.nextEOGEdges.firstOrNull { e -> e.getProperty(Properties.INDEX) == 0 }, + n.nextEOGEdges.filter { e -> e.getProperty(Properties.INDEX) != 0 } + ) + } else { + Pair(null, n.nextEOGEdges) } + + if (unreachableEdge != null) { + // This edge is definitely unreachable + state.push(unreachableEdge, ReachabilityLattice(Reachability.UNREACHABLE)) } + + // For all other edges, we simply propagate the reachability property of the edge which + // made us come here. + remainingEdges.forEach { state.push(it, state[enteringEdge]) } } /** Implements the [Lattice] over reachability properties: TOP | REACHABLE | UNREACHABLE | BOTTOM */ class ReachabilityLattice(override val elements: Reachability) : Lattice(elements) { override fun lub(other: Lattice?) = ReachabilityLattice(maxOf(this.elements, other?.elements ?: Reachability.BOTTOM)) + override fun duplicate() = ReachabilityLattice(this.elements) override fun compareTo(other: Lattice?) = this.elements.compareTo(other?.elements ?: Reachability.BOTTOM) } +/** The ordering will be as follows: BOTTOM (no information) < UNREACHABLE < REACHABLE < TOP */ enum class Reachability { BOTTOM, UNREACHABLE, diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt index b9f8532ba3..33f48efcbd 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/LanguageTraits.kt @@ -202,11 +202,22 @@ interface HasUnknownType : LanguageTrait { * evaluation if the logical result is already known: '&&', '||' in Java or 'and','or' in Python */ interface HasShortCircuitOperators : LanguageTrait { - // '&&', 'and', '^' + /** + * Operations which only execute the rhs of a binary operation if the lhs is `true`. Typically, + * these are `&&`, `and` or `^` + */ val conjunctiveOperators: List - // '||', 'or', 'v' + + /** + * Operations which only execute the rhs of a binary operation if the lhs is `false`. Typically, + * these are `||`, `or` or `v` + */ val disjunctiveOperators: List + /** + * The union of [conjunctiveOperators] and [disjunctiveOperators], i.e., all binary operators of + * this language which result in some kind of branching behavior. + */ val operatorCodes: Set get() = conjunctiveOperators.union(disjunctiveOperators) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt index 804e71e0d3..9360beeb27 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/BranchingNode.kt @@ -28,5 +28,5 @@ package de.fraunhofer.aisec.cpg.graph /** A node triggering a conditional execution of other code. */ interface BranchingNode { /** The node which affects the next EOG edge. Typically, this is a condition or similar. */ - val branchingDecision: Node? + val branchedBy: Node? } 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 b8dea6106c..f3d8d99455 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 @@ -58,10 +58,12 @@ fun MetadataProvider.newLiteral( } /** - * Creates a new [BinaryOperator]. The [MetadataProvider] receiver will be used to fill different - * meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin requires - * an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional prepended - * argument. + * Creates a new [BinaryOperator] or a [ShortCircuitOperator] if the language implements + * [HasShortCircuitOperators] and if the [operatorCode] is contained in + * [HasShortCircuitOperators.operatorCodes]. The [MetadataProvider] receiver will be used to fill + * different meta-data using [Node.applyMetadata]. Calling this extension function outside of Kotlin + * requires an appropriate [MetadataProvider], such as a [LanguageFrontend] as an additional + * prepended argument. */ @JvmOverloads fun MetadataProvider.newBinaryOperator( diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt index d1b99a1cc4..a07ab7525e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/CatchClause.kt @@ -36,7 +36,7 @@ class CatchClause : Statement(), BranchingNode { @AST var body: CompoundStatement? = null - override val branchingDecision: Node? + override val branchedBy: Node? get() = parameter override fun equals(other: Any?): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index 915ec802e2..05cce46a10 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -50,7 +50,7 @@ class ForEachStatement : Statement(), BranchingNode, StatementHolder { /** This field contains the body of the loop. */ @AST var statement: Statement? = null - override val branchingDecision: Node? + override val branchedBy: Node? get() = iterable override var statementEdges: MutableList> diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt index 8a4dec9453..24a3a1af34 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForStatement.kt @@ -43,7 +43,7 @@ class ForStatement : Statement(), BranchingNode { @AST var iterationStatement: Statement? = null - override val branchingDecision: Node? + override val branchedBy: Node? get() = condition ?: conditionDeclaration override fun equals(other: Any?): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt index d4596ba6c2..602348d241 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/IfStatement.kt @@ -45,7 +45,7 @@ class IfStatement : Statement(), BranchingNode, ArgumentHolder { /** The condition to be evaluated. */ @AST var condition: Expression? = null - override val branchingDecision: Node? + override val branchedBy: Node? get() = condition ?: conditionDeclaration /** C++ constexpr construct. */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt index 1c035667f1..f9e39ca7e4 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/SwitchStatement.kt @@ -53,7 +53,7 @@ class SwitchStatement : Statement(), BranchingNode { */ @AST var statement: Statement? = null - override val branchingDecision: Node? + override val branchedBy: Node? get() = selector override fun equals(other: Any?): Boolean { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt index 5cc42e47f2..14b08f2180 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/WhileStatement.kt @@ -48,7 +48,7 @@ class WhileStatement : Statement(), BranchingNode, ArgumentHolder { */ @AST var statement: Statement? = null - override val branchingDecision: Node? + override val branchedBy: Node? get() = condition ?: conditionDeclaration override fun toString(): String { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt index ce01dc71a3..800d4c0982 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ConditionalExpression.kt @@ -93,7 +93,7 @@ class ConditionalExpression : Expression(), HasType.TypeListener, ArgumentHolder .build() } - override val branchingDecision: Node + override val branchedBy: Node get() = condition override fun addArgument(expression: Expression) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt index 5a72a80eb3..161b8ccc2a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/expressions/ShortCircuitOperator.kt @@ -29,6 +29,6 @@ import de.fraunhofer.aisec.cpg.graph.BranchingNode import de.fraunhofer.aisec.cpg.graph.Node class ShortCircuitOperator : BinaryOperator(), BranchingNode { - override val branchingDecision: Node + override val branchedBy: Node get() = lhs } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index dbccaade6e..edcc5bc00f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -40,6 +40,7 @@ import de.fraunhofer.aisec.cpg.helpers.State import de.fraunhofer.aisec.cpg.helpers.Worklist import de.fraunhofer.aisec.cpg.passes.order.DependsOn +/** This pass builds the Control Dependence Graph (CDG) by iterating through the EOG. */ @DependsOn(EvaluationOrderGraphPass::class) class ControlDependenceGraphPass : Pass() { override fun cleanup() { @@ -50,6 +51,17 @@ class ControlDependenceGraphPass : Pass() { t.functions.forEach(::handle) } + /** + * Computes the CDG for the given [functionDecl]. It performs the following steps: + * 1) Compute the "parent branching node" for each node and through which path the node is + * reached + * 2) Find out which branch of a [BranchingNode] is actually conditional. The other ones aren't. + * 3) For each node: 3.a) Check if the node is reachable through an unconditional path of its + * parent [BranchingNode] or through all the conditional paths. 3.b) Move the node "one layer + * up" by finding the parent node of the current [BranchingNode] and changing it to this + * parent node and the path(s) through which the [BranchingNode] node is reachable. 3.c) + * Repeat step 3) until you cannot move the node upwards in the CDG anymore. + */ private fun handle(functionDecl: FunctionDeclaration) { // Maps nodes to their "cdg parent" (i.e. the dominator) and also has the information // through which path it is reached. If all outgoing paths of the node's dominator result in @@ -145,40 +157,49 @@ class ControlDependenceGraphPass : Pass() { } .toTypedArray() ) +} - companion object { - @JvmStatic - fun handleEdge( - currentEdge: PropertyEdge, - currentState: State>>, - currentWorklist: Worklist, Node, Map>> - ): Pair>>, Boolean> { - // Check if we start in a branching node and if this edge leads to the conditional - // branch. In this case, the next node will move "one layer downwards" in the CDG. - if (currentEdge.start is BranchingNode) { // && currentEdge.isConditionalBranch()) { - // We start in a branching node and end in one of the branches, so we have the - // following state: - // for the branching node "start", we have a path through "end". - currentState.push( - currentEdge.end, - PrevEOGLattice(mapOf(Pair(currentEdge.start, setOf(currentEdge.end)))) - ) - } else { - // We did not start in a branching node, so for the next node, we have the same path - // (last branching + first end node) as for the start node of this edge. - // If there is no state for the start node (most likely, this is the case for the - // first edge in a function), we generate a new state where we start in "start" end - // have "end" as the first node in the "branch". - val state = - PrevEOGLattice( - currentState[currentEdge.start]?.elements - ?: mapOf(Pair(currentEdge.start, setOf(currentEdge.end))) - ) - currentState.push(currentEdge.end, state) - } - return Pair(currentState, true) - } +/** + * This method is executed for each EOG edge which is in the worklist. [currentEdge] is the edge to + * process, [currentState] contains the state which was observed before arriving here. + * + * This method modifies the state for the next eog edge as follows: + * - If [currentEdge] starts in a [BranchingNode], the end node depends on the start node. We modify + * the state to express that "the end node depends on the start node and is reachable through the + * path starting at the end node". + * - For all other starting nodes, we copy the state of the start node to the end node. + * + * Returns the updated state and true because we always expect an update of the state. + */ +fun handleEdge( + currentEdge: PropertyEdge, + currentState: State>>, + currentWorklist: Worklist, Node, Map>> +): Pair>>, Boolean> { + // Check if we start in a branching node and if this edge leads to the conditional + // branch. In this case, the next node will move "one layer downwards" in the CDG. + if (currentEdge.start is BranchingNode) { // && currentEdge.isConditionalBranch()) { + // We start in a branching node and end in one of the branches, so we have the + // following state: + // for the branching node "start", we have a path through "end". + currentState.push( + currentEdge.end, + PrevEOGLattice(mapOf(Pair(currentEdge.start, setOf(currentEdge.end)))) + ) + } else { + // We did not start in a branching node, so for the next node, we have the same path + // (last branching + first end node) as for the start node of this edge. + // If there is no state for the start node (most likely, this is the case for the + // first edge in a function), we generate a new state where we start in "start" end + // have "end" as the first node in the "branch". + val state = + PrevEOGLattice( + currentState[currentEdge.start]?.elements + ?: mapOf(Pair(currentEdge.start, setOf(currentEdge.end))) + ) + currentState.push(currentEdge.end, state) } + return Pair(currentState, true) } /** @@ -235,7 +256,7 @@ class PrevEOGLattice(override val elements: Map>) : } /** - * A state which actually holds a state for all [PropertyEdge]s, one only for declarations and one - * for ReturnStatements. + * A state which actually holds a state for all [PropertyEdge]s. It maps the node to its + * [BranchingNode]-parent and the path through which it is reached. */ class PrevEOGState : State>>() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index d0348afb0e..c299994505 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -92,207 +92,203 @@ class ControlFlowSensitiveDFGPass : Pass() { varDecl.clearNextDFG() } } +} - companion object { - @JvmStatic - fun isLoopPoint(node: Node) = - node is ForStatement || - node is WhileStatement || - node is ForEachStatement || - node is DoStatement || - node is GotoStatement || - node is ContinueStatement || - (node is Literal<*> && node in node.nextEOG) - - @JvmStatic - fun transfer( - currentNode: Node, - state: State>, - worklist: Worklist> - ): Pair>, Boolean> { - // We will set this if we write to a variable - val writtenDecl: Declaration? - - var expectedUpdate = isLoopPoint(currentNode) - - val doubleState = state as DFGPassState - - val initializer = (currentNode as? VariableDeclaration)?.initializer - if (initializer != null) { - expectedUpdate = true - // A variable declaration with an initializer => The initializer flows to the - // declaration. - // We also wrote something to this variable declaration - state.push(currentNode, PowersetLattice(setOf(initializer))) - - doubleState.pushToDeclarationsState( - currentNode, - PowersetLattice(setOf(currentNode)) - ) - } else if (currentNode is AssignExpression) { - expectedUpdate = true - // It's an assignment which can have one or multiple things on the lhs and on the - // rhs. The lhs could be a declaration or a reference (or multiple of these things). - // The rhs can be anything. The rhs flows to the respective lhs. To identify the - // correct mapping, we use the "assignments" property which already searches for us. - currentNode.assignments.forEach { assignment -> - // This was the last write to the respective declaration. - (assignment.target as? Declaration - ?: (assignment.target as? DeclaredReferenceExpression)?.refersTo) - ?.let { - doubleState.declarationsState[it] = - PowersetLattice(setOf(assignment.target as Node)) - } - } - } else if (isIncOrDec(currentNode)) { - expectedUpdate = true - // Increment or decrement => Add the prevWrite of the input to the input. After the - // operation, the prevWrite of the input's variable is this node. - val input = (currentNode as UnaryOperator).input as DeclaredReferenceExpression - // We write to the variable in the input - writtenDecl = input.refersTo - - if (writtenDecl != null) { - state.push(input, doubleState.declarationsState[writtenDecl]) - doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(input)) +fun isLoopPoint(node: Node) = + node is ForStatement || + node is WhileStatement || + node is ForEachStatement || + node is DoStatement || + node is GotoStatement || + node is ContinueStatement || + (node is Literal<*> && node in node.nextEOG) + +/** + * Computes the previous write access of [node] if it is a [DeclaredReferenceExpression] or + * [ValueDeclaration] based on the given [state] (which maps all variables to its last write + * instruction). It also updates the [state] if [node] performs a write-operation to a variable. + * + * It further determines unnecessary implicit return statement which are added by some frontends + * even if every path reaching this point already contains a return statement. + */ +fun transfer( + currentNode: Node, + state: State>, + worklist: Worklist> +): Pair>, Boolean> { + // We will set this if we write to a variable + val writtenDecl: Declaration? + + var expectedUpdate = isLoopPoint(currentNode) + + val doubleState = state as DFGPassState + + val initializer = (currentNode as? VariableDeclaration)?.initializer + if (initializer != null) { + expectedUpdate = true + // A variable declaration with an initializer => The initializer flows to the + // declaration. + // We also wrote something to this variable declaration + state.push(currentNode, PowersetLattice(setOf(initializer))) + + doubleState.pushToDeclarationsState(currentNode, PowersetLattice(setOf(currentNode))) + } else if (currentNode is AssignExpression) { + expectedUpdate = true + // It's an assignment which can have one or multiple things on the lhs and on the + // rhs. The lhs could be a declaration or a reference (or multiple of these things). + // The rhs can be anything. The rhs flows to the respective lhs. To identify the + // correct mapping, we use the "assignments" property which already searches for us. + currentNode.assignments.forEach { assignment -> + // This was the last write to the respective declaration. + (assignment.target as? Declaration + ?: (assignment.target as? DeclaredReferenceExpression)?.refersTo) + ?.let { + doubleState.declarationsState[it] = + PowersetLattice(setOf(assignment.target as Node)) } - } else if (isSimpleAssignment(currentNode)) { - expectedUpdate = true + } + } else if (isIncOrDec(currentNode)) { + expectedUpdate = true + // Increment or decrement => Add the prevWrite of the input to the input. After the + // operation, the prevWrite of the input's variable is this node. + val input = (currentNode as UnaryOperator).input as DeclaredReferenceExpression + // We write to the variable in the input + writtenDecl = input.refersTo + + if (writtenDecl != null) { + state.push(input, doubleState.declarationsState[writtenDecl]) + doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(input)) + } + } else if (isSimpleAssignment(currentNode)) { + expectedUpdate = true - // Only the lhs is the last write statement here and the variable which is written - // to. - writtenDecl = - ((currentNode as BinaryOperator).lhs as DeclaredReferenceExpression).refersTo + // Only the lhs is the last write statement here and the variable which is written + // to. + writtenDecl = ((currentNode as BinaryOperator).lhs as DeclaredReferenceExpression).refersTo - if (writtenDecl != null) { - doubleState.declarationsState[writtenDecl] = - PowersetLattice(setOf(currentNode.lhs)) - } - } else if (isCompoundAssignment(currentNode)) { - expectedUpdate = true - // We write to the lhs, but it also serves as an input => We first get all previous - // writes to the lhs and then add the flow from lhs and rhs to the current node. - - // The write operation goes to the variable in the lhs - writtenDecl = - ((currentNode as BinaryOperator).lhs as? DeclaredReferenceExpression)?.refersTo - - if (writtenDecl != null) { - // Data flows from the last writes to the lhs variable to this node - state.push(currentNode.lhs, doubleState.declarationsState[writtenDecl]) - - // The whole current node is the place of the last update, not (only) the lhs! - doubleState.declarationsState[writtenDecl] = - PowersetLattice(setOf(currentNode.lhs)) - } - } else if ((currentNode as? DeclaredReferenceExpression)?.access == AccessValues.READ) { - // We can only find a change if there's a state for the variable - doubleState.declarationsState[currentNode.refersTo]?.let { - expectedUpdate = true - // We only read the variable => Get previous write which have been collected in - // the other steps - state.push(currentNode, it) - } - } else if (currentNode is ForEachStatement && currentNode.variable != null) { - expectedUpdate = true - // The VariableDeclaration in the ForEachStatement doesn't have an initializer, so - // the "normal" case won't work. We handle this case separately here... - // This is what we write to the declaration - val iterable = currentNode.iterable as? Expression - - val writtenTo = - when (currentNode.variable) { - is DeclarationStatement -> - (currentNode.variable as DeclarationStatement).singleDeclaration - else -> currentNode.variable - } - - // We wrote something to this variable declaration - writtenDecl = - when (writtenTo) { - is Declaration -> writtenTo - is DeclaredReferenceExpression -> writtenTo.refersTo - else -> { - log.error( - "The variable of type ${writtenTo?.javaClass} is not yet supported in the foreach loop" - ) - null - } - } - - if (writtenTo is DeclaredReferenceExpression) { - // This is a special case: We add the nextEOGEdge which goes out of the loop but - // with the old previousWrites map. - val nodesOutsideTheLoop = - currentNode.nextEOGEdges.filter { - it.getProperty(Properties.UNREACHABLE) != true && - it.end != currentNode.statement && - it.end !in currentNode.statement.allChildren() - } - nodesOutsideTheLoop - .map { it.end } - .forEach { worklist.push(it, state.duplicate()) } - } + if (writtenDecl != null) { + doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(currentNode.lhs)) + } + } else if (isCompoundAssignment(currentNode)) { + expectedUpdate = true + // We write to the lhs, but it also serves as an input => We first get all previous + // writes to the lhs and then add the flow from lhs and rhs to the current node. - iterable?.let { - writtenTo?.let { - state.push(writtenTo, PowersetLattice(setOf(iterable))) - // Add the variable declaration (or the reference) to the list of previous - // write nodes in this path - state.declarationsState[writtenDecl] = PowersetLattice(setOf(writtenTo)) - } - } - } else if (currentNode is FunctionDeclaration) { - // We have to add the parameters - currentNode.parameters.forEach { - doubleState.declarationsState.push(it, PowersetLattice(setOf(it))) + // The write operation goes to the variable in the lhs + writtenDecl = + ((currentNode as BinaryOperator).lhs as? DeclaredReferenceExpression)?.refersTo + + if (writtenDecl != null) { + // Data flows from the last writes to the lhs variable to this node + state.push(currentNode.lhs, doubleState.declarationsState[writtenDecl]) + + // The whole current node is the place of the last update, not (only) the lhs! + doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(currentNode.lhs)) + } + } else if ((currentNode as? DeclaredReferenceExpression)?.access == AccessValues.READ) { + // We can only find a change if there's a state for the variable + doubleState.declarationsState[currentNode.refersTo]?.let { + expectedUpdate = true + // We only read the variable => Get previous write which have been collected in + // the other steps + state.push(currentNode, it) + } + } else if (currentNode is ForEachStatement && currentNode.variable != null) { + expectedUpdate = true + // The VariableDeclaration in the ForEachStatement doesn't have an initializer, so + // the "normal" case won't work. We handle this case separately here... + // This is what we write to the declaration + val iterable = currentNode.iterable as? Expression + + val writtenTo = + when (currentNode.variable) { + is DeclarationStatement -> + (currentNode.variable as DeclarationStatement).singleDeclaration + else -> currentNode.variable + } + + // We wrote something to this variable declaration + writtenDecl = + when (writtenTo) { + is Declaration -> writtenTo + is DeclaredReferenceExpression -> writtenTo.refersTo + else -> { + Pass.log.error( + "The variable of type ${writtenTo?.javaClass} is not yet supported in the foreach loop" + ) + null } - } else if (currentNode is ReturnStatement) { - doubleState.returnStatements.push(currentNode, PowersetLattice(setOf(currentNode))) } - return Pair(state, expectedUpdate) + + if (writtenTo is DeclaredReferenceExpression) { + // This is a special case: We add the nextEOGEdge which goes out of the loop but + // with the old previousWrites map. + val nodesOutsideTheLoop = + currentNode.nextEOGEdges.filter { + it.getProperty(Properties.UNREACHABLE) != true && + it.end != currentNode.statement && + it.end !in currentNode.statement.allChildren() + } + nodesOutsideTheLoop.map { it.end }.forEach { worklist.push(it, state.duplicate()) } } - /** - * Checks if the node performs an operation and an assignment at the same time e.g. with the - * operators +=, -=, *=, ... - */ - private fun isCompoundAssignment(currentNode: Node) = - currentNode is BinaryOperator && - currentNode.operatorCode in - (currentNode.language?.compoundAssignmentOperators ?: setOf()) && - (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null - - /** Checks if the node is a simple assignment of the form `var = ...` */ - private fun isSimpleAssignment(currentNode: Node) = - currentNode is BinaryOperator && - currentNode.operatorCode == "=" && - (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null - - /** Checks if the node is an increment or decrement operator (e.g. i++, i--, ++i, --i) */ - private fun isIncOrDec(currentNode: Node) = - currentNode is UnaryOperator && - (currentNode.operatorCode == "++" || currentNode.operatorCode == "--") && - (currentNode.input as? DeclaredReferenceExpression)?.refersTo != null + iterable?.let { + writtenTo?.let { + state.push(writtenTo, PowersetLattice(setOf(iterable))) + // Add the variable declaration (or the reference) to the list of previous + // write nodes in this path + state.declarationsState[writtenDecl] = PowersetLattice(setOf(writtenTo)) + } + } + } else if (currentNode is FunctionDeclaration) { + // We have to add the parameters + currentNode.parameters.forEach { + doubleState.declarationsState.push(it, PowersetLattice(setOf(it))) + } + } else if (currentNode is ReturnStatement) { + doubleState.returnStatements.push(currentNode, PowersetLattice(setOf(currentNode))) } + return Pair(state, expectedUpdate) +} - /** - * Removes the DFG edges for a potential implicit return statement if it is not in - * [reachableReturnStatements]. - */ - private fun removeUnreachableImplicitReturnStatement( - node: Node, - reachableReturnStatements: Collection - ) { - val lastStatement = - ((node as? FunctionDeclaration)?.body as? CompoundStatement)?.statements?.lastOrNull() - if ( - lastStatement is ReturnStatement && - lastStatement.isImplicit && - lastStatement !in reachableReturnStatements - ) - lastStatement.removeNextDFG(node) - } +/** + * Checks if the node performs an operation and an assignment at the same time e.g. with the + * operators +=, -=, *=, ... + */ +private fun isCompoundAssignment(currentNode: Node) = + currentNode is BinaryOperator && + currentNode.operatorCode in + (currentNode.language?.compoundAssignmentOperators ?: setOf()) && + (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null + +/** Checks if the node is a simple assignment of the form `var = ...` */ +private fun isSimpleAssignment(currentNode: Node) = + currentNode is BinaryOperator && + currentNode.operatorCode == "=" && + (currentNode.lhs as? DeclaredReferenceExpression)?.refersTo != null + +/** Checks if the node is an increment or decrement operator (e.g. i++, i--, ++i, --i) */ +private fun isIncOrDec(currentNode: Node) = + currentNode is UnaryOperator && + (currentNode.operatorCode == "++" || currentNode.operatorCode == "--") && + (currentNode.input as? DeclaredReferenceExpression)?.refersTo != null + +/** + * Removes the DFG edges for a potential implicit return statement if it is not in + * [reachableReturnStatements]. + */ +private fun removeUnreachableImplicitReturnStatement( + node: Node, + reachableReturnStatements: Collection +) { + val lastStatement = + ((node as? FunctionDeclaration)?.body as? CompoundStatement)?.statements?.lastOrNull() + if ( + lastStatement is ReturnStatement && + lastStatement.isImplicit && + lastStatement !in reachableReturnStatements + ) + lastStatement.removeNextDFG(node) } /** From 871530a740f88cd70b8d1dbb3df76becd929c5bf Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 31 May 2023 16:23:03 +0200 Subject: [PATCH 21/53] More review feedback --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 2 +- .../aisec/cpg/helpers/EOGWorklist.kt | 152 +++++++++--------- .../cpg/passes/ControlDependenceGraphPass.kt | 8 +- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 2 +- 4 files changed, 82 insertions(+), 82 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index c4896b84c0..dc141289c3 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -64,7 +64,7 @@ class UnreachableEOGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { for (firstEdge in node.nextEOGEdges) { startState.push(firstEdge, ReachabilityLattice(Reachability.REACHABLE)) } - val finalState = EOGWorklist().iterateEOG(node.nextEOGEdges, startState, ::transfer) + val finalState = iterateEOG(node.nextEOGEdges, startState, ::transfer) for ((key, value) in finalState) { if (value.elements == Reachability.UNREACHABLE) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index d5407c2201..4fd2e45a57 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -30,8 +30,14 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import java.util.IdentityHashMap /** - * An abstract class representing complete lattices, i.e., an ordered structure of values of type - * [T]. Implementations of this class have to implement the comparator, the least upper bound of two + * A complete lattices is an ordered structure of values of type [T]. [T] could be anything, e.g., a + * set, a new data structure (like a range), or anything else. [T] depends on the analysis and + * typically has to abstract the value for the specific purpose. + * + * This class is actually used to hold individual instances of the lattice's elements and to compute + * bigger elements depending on these two elements. + * + * Implementations of this class have to implement the comparator, the least upper bound of two * lattices. */ abstract class Lattice(open val elements: T) : Comparable?> { @@ -140,7 +146,7 @@ class Worklist() { private set /** A list of all nodes which have already been visited. */ - private val alreadySeen = mutableListOf() + private val alreadySeen = IdentitySet() constructor(globalState: MutableMap> = mutableMapOf()) : this() { this.globalState = globalState @@ -190,6 +196,7 @@ class Worklist() { /** Determines if there are still elements to analyze */ fun isNotEmpty() = nodeOrder.isNotEmpty() + /** Determines if there are no more elements to analyze */ fun isEmpty() = nodeOrder.isEmpty() @@ -215,89 +222,86 @@ class Worklist() { } } -/** A very simple implementation of the worklist algorithm. */ -class EOGWorklist { - /** - * Iterates through the worklist of the Evaluation Order Graph starting at [startNode] and with - * the [State] [startState]. For each node, the [transformation] is applied which should update - * the state. - * - * [transformation] receives the current [Node] popped from the worklist, the [State] at this - * node which is considered for this analysis and even the current [Worklist]. The worklist is - * given if we have to add more elements out-of-order e.g. because the EOG is traversed in an - * order which is not useful for this analysis. The [transformation] has to return the updated - * [State] and an indication if we expect that the state has changed. This is necessary because - * not every transition in the EOG will really lead to an update of the current state depending - * on the analysis. - */ - inline fun iterateEOG( - startNode: K, - startState: State, - transformation: (K, State, Worklist) -> Pair, Boolean> - ): State { - val worklist = Worklist(mutableMapOf(Pair(startNode, startState))) - worklist.push(startNode, startState) - - while (worklist.isNotEmpty()) { - val (nextNode, state) = worklist.pop() - - val (newState, expectedUpdate) = transformation(nextNode, state.duplicate(), worklist) - if (worklist.update(nextNode, newState) || !expectedUpdate) { - nextNode.nextEOG.forEach { if (it is K) worklist.push(it, newState.duplicate()) } - } +/** + * Iterates through the worklist of the Evaluation Order Graph starting at [startNode] and with the + * [State] [startState]. For each node, the [transformation] is applied which should update the + * state. + * + * [transformation] receives the current [Node] popped from the worklist, the [State] at this node + * which is considered for this analysis and even the current [Worklist]. The worklist is given if + * we have to add more elements out-of-order e.g. because the EOG is traversed in an order which is + * not useful for this analysis. The [transformation] has to return the updated [State] and an + * indication if we expect that the state has changed. This is necessary because not every + * transition in the EOG will really lead to an update of the current state depending on the + * analysis. + */ +inline fun iterateEOG( + startNode: K, + startState: State, + transformation: (K, State, Worklist) -> Pair, Boolean> +): State { + val worklist = Worklist(mutableMapOf(Pair(startNode, startState))) + worklist.push(startNode, startState) + + while (worklist.isNotEmpty()) { + val (nextNode, state) = worklist.pop() + + val (newState, expectedUpdate) = transformation(nextNode, state.duplicate(), worklist) + if (worklist.update(nextNode, newState) || !expectedUpdate) { + nextNode.nextEOG.forEach { if (it is K) worklist.push(it, newState.duplicate()) } } - return worklist.mop() } + return worklist.mop() +} - inline fun , V> iterateEOG( - startEdges: List, - startState: State, - transformation: - (PropertyEdge, State, Worklist) -> Pair, Boolean> - ): State { - val globalState = mutableMapOf>() - for (startEdge in startEdges) { - globalState[startEdge] = startState - } - val worklist = Worklist(globalState) - startEdges.forEach { worklist.push(it, startState) } +inline fun , V> iterateEOG( + startEdges: List, + startState: State, + transformation: + (PropertyEdge, State, Worklist) -> Pair, Boolean> +): State { + val globalState = mutableMapOf>() + for (startEdge in startEdges) { + globalState[startEdge] = startState + } + val worklist = Worklist(globalState) + startEdges.forEach { worklist.push(it, startState) } - while (worklist.isNotEmpty()) { - val (nextEdge, state) = worklist.pop() + while (worklist.isNotEmpty()) { + val (nextEdge, state) = worklist.pop() - val (newState, expectedUpdate) = transformation(nextEdge, state.duplicate(), worklist) - if (worklist.update(nextEdge, newState) || !expectedUpdate) { - nextEdge.end.nextEOGEdges.forEach { - if (it is K) worklist.push(it, newState.duplicate()) - } + val (newState, expectedUpdate) = transformation(nextEdge, state.duplicate(), worklist) + if (worklist.update(nextEdge, newState) || !expectedUpdate) { + nextEdge.end.nextEOGEdges.forEach { + if (it is K) worklist.push(it, newState.duplicate()) } } - return worklist.mop() } + return worklist.mop() +} - inline fun , N : Node, V> iterateEOGEN( - startEdges: List, - startState: State, - transformation: - (PropertyEdge, State, Worklist) -> Pair, Boolean> - ): State { - val globalState = mutableMapOf>() - for (startEdge in startEdges) { - globalState[startEdge] = startState - } - val worklist = Worklist(globalState) - startEdges.forEach { worklist.push(it, startState) } +inline fun , N : Node, V> iterateEOGEN( + startEdges: List, + startState: State, + transformation: + (PropertyEdge, State, Worklist) -> Pair, Boolean> +): State { + val globalState = mutableMapOf>() + for (startEdge in startEdges) { + globalState[startEdge] = startState + } + val worklist = Worklist(globalState) + startEdges.forEach { worklist.push(it, startState) } - while (worklist.isNotEmpty()) { - val (nextEdge, state) = worklist.pop() + while (worklist.isNotEmpty()) { + val (nextEdge, state) = worklist.pop() - val (newState, expectedUpdate) = transformation(nextEdge, state.duplicate(), worklist) - if (worklist.update(nextEdge, newState) || !expectedUpdate) { - nextEdge.end.nextEOGEdges.forEach { - if (it is K) worklist.push(it, newState.duplicate()) - } + val (newState, expectedUpdate) = transformation(nextEdge, state.duplicate(), worklist) + if (worklist.update(nextEdge, newState) || !expectedUpdate) { + nextEdge.end.nextEOGEdges.forEach { + if (it is K) worklist.push(it, newState.duplicate()) } } - return worklist.mop() } + return worklist.mop() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 942902ceee..9cabbe2bbc 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -35,10 +35,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.functions import de.fraunhofer.aisec.cpg.graph.statements.IfStatement -import de.fraunhofer.aisec.cpg.helpers.EOGWorklist -import de.fraunhofer.aisec.cpg.helpers.Lattice -import de.fraunhofer.aisec.cpg.helpers.State -import de.fraunhofer.aisec.cpg.helpers.Worklist +import de.fraunhofer.aisec.cpg.helpers.* import de.fraunhofer.aisec.cpg.passes.order.DependsOn /** This pass builds the Control Dependence Graph (CDG) by iterating through the EOG. */ @@ -72,8 +69,7 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : TranslationUnit functionDecl, PrevEOGLattice(mapOf(Pair(functionDecl, setOf(functionDecl)))) ) - val finalState = - EOGWorklist().iterateEOGEN(functionDecl.nextEOGEdges, startState, ::handleEdge) + val finalState = iterateEOGEN(functionDecl.nextEOGEdges, startState, ::handleEdge) val branchingNodeConditionals = getBranchingNodeConditions(functionDecl) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 9d3c28924f..896561f333 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -60,7 +60,7 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni protected fun handle(node: Node) { if (node is FunctionDeclaration) { clearFlowsOfVariableDeclarations(node) - val finalState = EOGWorklist().iterateEOG(node, DFGPassState(), ::transfer) + val finalState = iterateEOG(node, DFGPassState(), ::transfer) removeUnreachableImplicitReturnStatement( node, From 73b741bcd6f2003a92669b9d0fe920f216372d06 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Wed, 7 Jun 2023 09:51:49 +0200 Subject: [PATCH 22/53] Update cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt Co-authored-by: Christian Banse --- .../main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index 4fd2e45a57..982b72e067 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -30,7 +30,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import java.util.IdentityHashMap /** - * A complete lattices is an ordered structure of values of type [T]. [T] could be anything, e.g., a + * A complete lattice is an ordered structure of values of type [T]. [T] could be anything, e.g., a * set, a new data structure (like a range), or anything else. [T] depends on the analysis and * typically has to abstract the value for the specific purpose. * From cb9f2a2c72b2f3e62557c5dc6b3814048a96adf9 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 7 Jun 2023 10:00:27 +0200 Subject: [PATCH 23/53] Merge two iterateEOG functions --- .../aisec/cpg/helpers/EOGWorklist.kt | 31 ++----------------- .../cpg/passes/ControlDependenceGraphPass.kt | 2 +- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index 982b72e067..952238dab0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -254,37 +254,10 @@ inline fun iterateEOG( return worklist.mop() } -inline fun , V> iterateEOG( - startEdges: List, - startState: State, - transformation: - (PropertyEdge, State, Worklist) -> Pair, Boolean> -): State { - val globalState = mutableMapOf>() - for (startEdge in startEdges) { - globalState[startEdge] = startState - } - val worklist = Worklist(globalState) - startEdges.forEach { worklist.push(it, startState) } - - while (worklist.isNotEmpty()) { - val (nextEdge, state) = worklist.pop() - - val (newState, expectedUpdate) = transformation(nextEdge, state.duplicate(), worklist) - if (worklist.update(nextEdge, newState) || !expectedUpdate) { - nextEdge.end.nextEOGEdges.forEach { - if (it is K) worklist.push(it, newState.duplicate()) - } - } - } - return worklist.mop() -} - -inline fun , N : Node, V> iterateEOGEN( +inline fun , N : Any, V> iterateEOG( startEdges: List, startState: State, - transformation: - (PropertyEdge, State, Worklist) -> Pair, Boolean> + transformation: (K, State, Worklist) -> Pair, Boolean> ): State { val globalState = mutableMapOf>() for (startEdge in startEdges) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 9cabbe2bbc..9f9fd4996b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -69,7 +69,7 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : TranslationUnit functionDecl, PrevEOGLattice(mapOf(Pair(functionDecl, setOf(functionDecl)))) ) - val finalState = iterateEOGEN(functionDecl.nextEOGEdges, startState, ::handleEdge) + val finalState = iterateEOG(functionDecl.nextEOGEdges, startState, ::handleEdge) val branchingNodeConditionals = getBranchingNodeConditions(functionDecl) From 4ecc621a22005fd03e54996ff80c2cfa1895db3c Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Sun, 11 Jun 2023 17:15:14 +0200 Subject: [PATCH 24/53] Fluent DSL for loops --- .../aisec/cpg/graph/builder/Fluent.kt | 31 +++++++++++++++++- .../cpg/graph/statements/ForEachStatement.kt | 32 +------------------ .../passes/ControlDependenceGraphPassTest.kt | 6 ++-- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 631d5319ad..4085e7ec72 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -591,9 +591,10 @@ fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundState return node } + /** * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the - * [WhileStatement.statement] of the nearest enclosing [WhileStatement]. The [init] block can be + * [ForEachStatement.statement] of the nearest enclosing [ForEachStatement]. The [init] block can be * used to create further sub-nodes as well as configuring the created node itself. */ context(ForEachStatement) @@ -606,6 +607,34 @@ fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundState return node } +/** + * Creates a new [Statement] in the Fluent Node DSL and sets it to the [ForEachStatement.variable] + * of the nearest enclosing [ForEachStatement]. The [init] block can be used to create further + * sub-nodes as well as configuring the created node itself. + */ +context(ForEachStatement) + +fun LanguageFrontend.loopVariable(init: () -> Statement): Statement { + val s = init() + variable = s + + return s +} + +/** + * Creates a new [Statement] in the Fluent Node DSL and sets it to the [ForEachStatement.variable] + * of the nearest enclosing [ForEachStatement]. The [init] block can be used to create further + * sub-nodes as well as configuring the created node itself. + */ +context(ForEachStatement) + +fun LanguageFrontend.loopIterable(init: () -> Statement): Statement { + val s = init() + iterable = s + + return s +} + /** * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the * [SwitchStatement.statement] of the nearest enclosing [SwitchStatement]. The [init] block can be diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index 05cce46a10..fe4508d5a5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -26,11 +26,10 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.* -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import java.util.Objects -class ForEachStatement : Statement(), BranchingNode, StatementHolder { +class ForEachStatement : Statement(), BranchingNode { /** * This field contains the iteration variable of the loop. It can be either a new variable * declaration or a reference to an existing variable. @@ -53,35 +52,6 @@ class ForEachStatement : Statement(), BranchingNode, StatementHolder { override val branchedBy: Node? get() = iterable - override var statementEdges: MutableList> - get() { - val statements = mutableListOf>() - variable?.let { statements.add(PropertyEdge(this, it)) } - iterable?.let { statements.add(PropertyEdge(this, it)) } - statement?.let { statements.add(PropertyEdge(this, it)) } - return statements - } - set(value) { - // Nothing to do here - } - - override fun addStatement(s: Statement) { - if (variable == null) { - variable = s - } else if (iterable == null) { - iterable = s - } else if (statement == null) { - statement = s - } else if (statement !is CompoundStatement) { - val newStmt = newCompoundStatement() - statement?.let { newStmt.addStatement(it) } - newStmt.addStatement(s) - statement = newStmt - } else { - (statement as? CompoundStatement)?.addStatement(s) - } - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ForEachStatement) return false diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt index e34f724ce8..76f1c58bfe 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -175,8 +175,10 @@ class ControlDependenceGraphPassTest { body { declare { variable("i", t("int")) { literal(0, t("int")) } } forEachStmt { - declare { variable("loopVar", t("string")) } - call("magicFunction") + loopVariable { + declare { variable("loopVar", t("string")) } + } + loopIterable { call("magicFunction") } loopBody { call("printf") { addArgument(literal("loop: \${}\n", t("string"))) From c79d7cb9e73fda94222f62b21de88d00fa35efa6 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 13 Jun 2023 10:53:18 +0200 Subject: [PATCH 25/53] Fix redundant stuff --- .../cpg/passes/ControlDependenceGraphPassTest.kt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt index 76f1c58bfe..bc9020679e 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -140,18 +140,16 @@ class ControlDependenceGraphPassTest { condition { ref("i") lt literal(1, t("int")) } thenStmt { ref("i") assign literal(1, t("int")) - call("printf") { - addArgument(literal("0\n", t("string"))) - } + call("printf") { literal("0\n", t("string")) } } } - call("printf") { addArgument(literal("1\n", t("string"))) } + call("printf") { literal("1\n", t("string")) } ifStmt { condition { ref("i") gt literal(0, t("int")) } thenStmt { ref("i") assign literal(2, t("int")) } elseStmt { ref("i") assign literal(3, t("int")) } } - call("printf") { addArgument(literal("2\n", t("string"))) } + call("printf") { literal("2\n", t("string")) } returnStmt { ref("i") } } } @@ -181,12 +179,12 @@ class ControlDependenceGraphPassTest { loopIterable { call("magicFunction") } loopBody { call("printf") { - addArgument(literal("loop: \${}\n", t("string"))) - addArgument(ref("loopVar")) + literal("loop: \${}\n", t("string")) + ref("loopVar") } } } - call("printf") { addArgument(literal("1\n", t("string"))) } + call("printf") { literal("1\n", t("string")) } returnStmt { ref("i") } } From 6ce7340b154a395bb534d292e0f6869797b89427 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 13 Jun 2023 11:57:43 +0200 Subject: [PATCH 26/53] Revert "Fluent DSL for loops" This reverts commit 4ecc621a22005fd03e54996ff80c2cfa1895db3c. --- .../aisec/cpg/graph/builder/Fluent.kt | 31 +----------------- .../cpg/graph/statements/ForEachStatement.kt | 32 ++++++++++++++++++- .../passes/ControlDependenceGraphPassTest.kt | 6 ++-- 3 files changed, 34 insertions(+), 35 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 4085e7ec72..631d5319ad 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -591,10 +591,9 @@ fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundState return node } - /** * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the - * [ForEachStatement.statement] of the nearest enclosing [ForEachStatement]. The [init] block can be + * [WhileStatement.statement] of the nearest enclosing [WhileStatement]. The [init] block can be * used to create further sub-nodes as well as configuring the created node itself. */ context(ForEachStatement) @@ -607,34 +606,6 @@ fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundState return node } -/** - * Creates a new [Statement] in the Fluent Node DSL and sets it to the [ForEachStatement.variable] - * of the nearest enclosing [ForEachStatement]. The [init] block can be used to create further - * sub-nodes as well as configuring the created node itself. - */ -context(ForEachStatement) - -fun LanguageFrontend.loopVariable(init: () -> Statement): Statement { - val s = init() - variable = s - - return s -} - -/** - * Creates a new [Statement] in the Fluent Node DSL and sets it to the [ForEachStatement.variable] - * of the nearest enclosing [ForEachStatement]. The [init] block can be used to create further - * sub-nodes as well as configuring the created node itself. - */ -context(ForEachStatement) - -fun LanguageFrontend.loopIterable(init: () -> Statement): Statement { - val s = init() - iterable = s - - return s -} - /** * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the * [SwitchStatement.statement] of the nearest enclosing [SwitchStatement]. The [init] block can be diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt index fe4508d5a5..05cce46a10 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/statements/ForEachStatement.kt @@ -26,10 +26,11 @@ package de.fraunhofer.aisec.cpg.graph.statements import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeclaredReferenceExpression import java.util.Objects -class ForEachStatement : Statement(), BranchingNode { +class ForEachStatement : Statement(), BranchingNode, StatementHolder { /** * This field contains the iteration variable of the loop. It can be either a new variable * declaration or a reference to an existing variable. @@ -52,6 +53,35 @@ class ForEachStatement : Statement(), BranchingNode { override val branchedBy: Node? get() = iterable + override var statementEdges: MutableList> + get() { + val statements = mutableListOf>() + variable?.let { statements.add(PropertyEdge(this, it)) } + iterable?.let { statements.add(PropertyEdge(this, it)) } + statement?.let { statements.add(PropertyEdge(this, it)) } + return statements + } + set(value) { + // Nothing to do here + } + + override fun addStatement(s: Statement) { + if (variable == null) { + variable = s + } else if (iterable == null) { + iterable = s + } else if (statement == null) { + statement = s + } else if (statement !is CompoundStatement) { + val newStmt = newCompoundStatement() + statement?.let { newStmt.addStatement(it) } + newStmt.addStatement(s) + statement = newStmt + } else { + (statement as? CompoundStatement)?.addStatement(s) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is ForEachStatement) return false diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt index bc9020679e..2d78e0de1e 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPassTest.kt @@ -173,10 +173,8 @@ class ControlDependenceGraphPassTest { body { declare { variable("i", t("int")) { literal(0, t("int")) } } forEachStmt { - loopVariable { - declare { variable("loopVar", t("string")) } - } - loopIterable { call("magicFunction") } + declare { variable("loopVar", t("string")) } + call("magicFunction") loopBody { call("printf") { literal("loop: \${}\n", t("string")) From 5a237ebdd40cb0175890faf0ed3fdaa4e80e34e7 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 13 Jun 2023 16:27:14 +0200 Subject: [PATCH 27/53] Get rid of "expectedUpdate" --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 2 +- .../aisec/cpg/helpers/EOGWorklist.kt | 6 ++--- .../cpg/passes/ControlDependenceGraphPass.kt | 2 +- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 27 ++++++++++++++----- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index dc141289c3..79e88cb0b3 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -64,7 +64,7 @@ class UnreachableEOGPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { for (firstEdge in node.nextEOGEdges) { startState.push(firstEdge, ReachabilityLattice(Reachability.REACHABLE)) } - val finalState = iterateEOG(node.nextEOGEdges, startState, ::transfer) + val finalState = iterateEOG(node.nextEOGEdges, startState, ::transfer) ?: return for ((key, value) in finalState) { if (value.elements == Reachability.UNREACHABLE) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index 952238dab0..fe59c4e204 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -211,7 +211,7 @@ class Worklist() { fun hasAlreadySeen(currentNode: K) = currentNode in alreadySeen /** Computes the meet over paths for all the states in [globalState]. */ - fun mop(): State { + fun mop(): State? { val firstKey = globalState.keys.firstOrNull() val state = globalState[firstKey] for ((_, v) in globalState) { @@ -239,7 +239,7 @@ inline fun iterateEOG( startNode: K, startState: State, transformation: (K, State, Worklist) -> Pair, Boolean> -): State { +): State? { val worklist = Worklist(mutableMapOf(Pair(startNode, startState))) worklist.push(startNode, startState) @@ -258,7 +258,7 @@ inline fun , N : Any, V> iterateEOG( startEdges: List, startState: State, transformation: (K, State, Worklist) -> Pair, Boolean> -): State { +): State? { val globalState = mutableMapOf>() for (startEdge in startEdges) { globalState[startEdge] = startState diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 9f9fd4996b..e269c21284 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -69,7 +69,7 @@ open class ControlDependenceGraphPass(ctx: TranslationContext) : TranslationUnit functionDecl, PrevEOGLattice(mapOf(Pair(functionDecl, setOf(functionDecl)))) ) - val finalState = iterateEOG(functionDecl.nextEOGEdges, startState, ::handleEdge) + val finalState = iterateEOG(functionDecl.nextEOGEdges, startState, ::handleEdge) ?: return val branchingNodeConditionals = getBranchingNodeConditions(functionDecl) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 896561f333..9a78be2b94 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.* import de.fraunhofer.aisec.cpg.graph.edge.Properties +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.statements.* import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.helpers.* @@ -60,11 +61,14 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni protected fun handle(node: Node) { if (node is FunctionDeclaration) { clearFlowsOfVariableDeclarations(node) - val finalState = iterateEOG(node, DFGPassState(), ::transfer) + val startState = DFGPassState>() + startState.declarationsState.push(node, PowersetLattice(setOf())) + val finalState = + iterateEOG(node.nextEOGEdges, startState, ::transfer) as? DFGPassState ?: return removeUnreachableImplicitReturnStatement( node, - (finalState as DFGPassState).returnStatements.values.flatMap { + finalState.returnStatements.values.flatMap { it.elements.filterIsInstance() } ) @@ -107,14 +111,15 @@ fun isLoopPoint(node: Node) = * even if every path reaching this point already contains a return statement. */ fun transfer( - currentNode: Node, + currentEdge: PropertyEdge, state: State>, - worklist: Worklist> + worklist: Worklist, Node, Set> ): Pair>, Boolean> { // We will set this if we write to a variable val writtenDecl: Declaration? + val currentNode = currentEdge.end - var expectedUpdate = isLoopPoint(currentNode) + var expectedUpdate = true // isLoopPoint(currentNode) val doubleState = state as DFGPassState @@ -180,7 +185,10 @@ fun transfer( // The whole current node is the place of the last update, not (only) the lhs! doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(currentNode.lhs)) } - } else if ((currentNode as? DeclaredReferenceExpression)?.access == AccessValues.READ) { + } else if ( + (currentNode as? DeclaredReferenceExpression)?.access == AccessValues.READ && + currentNode.refersTo is VariableDeclaration + ) { // We can only find a change if there's a state for the variable doubleState.declarationsState[currentNode.refersTo]?.let { expectedUpdate = true @@ -224,7 +232,7 @@ fun transfer( it.end != currentNode.statement && it.end !in currentNode.statement.allChildren() } - nodesOutsideTheLoop.map { it.end }.forEach { worklist.push(it, state.duplicate()) } + nodesOutsideTheLoop.forEach { worklist.push(it, state.duplicate()) } } iterable?.let { @@ -242,6 +250,11 @@ fun transfer( } } else if (currentNode is ReturnStatement) { doubleState.returnStatements.push(currentNode, PowersetLattice(setOf(currentNode))) + } else { + doubleState.declarationsState.push( + currentNode, + doubleState.declarationsState[currentEdge.start] + ) } return Pair(state, expectedUpdate) } From 2e6f440960d8bd9a6cbca74976d954fa322874a5 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Tue, 13 Jun 2023 16:53:22 +0200 Subject: [PATCH 28/53] Change method signatures --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 4 ++-- .../fraunhofer/aisec/cpg/helpers/EOGWorklist.kt | 17 +++++++---------- .../cpg/passes/ControlDependenceGraphPass.kt | 4 ++-- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 14 ++------------ 4 files changed, 13 insertions(+), 26 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index 79e88cb0b3..a3b01fdfcd 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -93,7 +93,7 @@ fun transfer( currentEdge: PropertyEdge, currentState: State, Reachability>, currentWorklist: Worklist, PropertyEdge, Reachability> -): Pair, Reachability>, Boolean> { +): State, Reachability> { val currentNode = currentEdge.end if (currentNode is IfStatement) { handleIfStatement(currentEdge, currentNode, currentState) @@ -105,7 +105,7 @@ fun transfer( currentNode.nextEOGEdges.forEach { currentState.push(it, currentState[currentEdge]) } } - return Pair(currentState, true) + return currentState } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index fe59c4e204..7083dd7fcf 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -230,15 +230,12 @@ class Worklist() { * [transformation] receives the current [Node] popped from the worklist, the [State] at this node * which is considered for this analysis and even the current [Worklist]. The worklist is given if * we have to add more elements out-of-order e.g. because the EOG is traversed in an order which is - * not useful for this analysis. The [transformation] has to return the updated [State] and an - * indication if we expect that the state has changed. This is necessary because not every - * transition in the EOG will really lead to an update of the current state depending on the - * analysis. + * not useful for this analysis. The [transformation] has to return the updated [State]. */ inline fun iterateEOG( startNode: K, startState: State, - transformation: (K, State, Worklist) -> Pair, Boolean> + transformation: (K, State, Worklist) -> State ): State? { val worklist = Worklist(mutableMapOf(Pair(startNode, startState))) worklist.push(startNode, startState) @@ -246,8 +243,8 @@ inline fun iterateEOG( while (worklist.isNotEmpty()) { val (nextNode, state) = worklist.pop() - val (newState, expectedUpdate) = transformation(nextNode, state.duplicate(), worklist) - if (worklist.update(nextNode, newState) || !expectedUpdate) { + val newState = transformation(nextNode, state.duplicate(), worklist) + if (worklist.update(nextNode, newState)) { nextNode.nextEOG.forEach { if (it is K) worklist.push(it, newState.duplicate()) } } } @@ -257,7 +254,7 @@ inline fun iterateEOG( inline fun , N : Any, V> iterateEOG( startEdges: List, startState: State, - transformation: (K, State, Worklist) -> Pair, Boolean> + transformation: (K, State, Worklist) -> State ): State? { val globalState = mutableMapOf>() for (startEdge in startEdges) { @@ -269,8 +266,8 @@ inline fun , N : Any, V> iterateEOG( while (worklist.isNotEmpty()) { val (nextEdge, state) = worklist.pop() - val (newState, expectedUpdate) = transformation(nextEdge, state.duplicate(), worklist) - if (worklist.update(nextEdge, newState) || !expectedUpdate) { + val newState = transformation(nextEdge, state.duplicate(), worklist) + if (worklist.update(nextEdge, newState)) { nextEdge.end.nextEOGEdges.forEach { if (it is K) worklist.push(it, newState.duplicate()) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index e269c21284..15a972ea79 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -172,7 +172,7 @@ fun handleEdge( currentEdge: PropertyEdge, currentState: State>>, currentWorklist: Worklist, Node, Map>> -): Pair>>, Boolean> { +): State>> { // Check if we start in a branching node and if this edge leads to the conditional // branch. In this case, the next node will move "one layer downwards" in the CDG. if (currentEdge.start is BranchingNode) { // && currentEdge.isConditionalBranch()) { @@ -196,7 +196,7 @@ fun handleEdge( ) currentState.push(currentEdge.end, state) } - return Pair(currentState, true) + return currentState } /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 9a78be2b94..19a6bbb532 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -114,18 +114,15 @@ fun transfer( currentEdge: PropertyEdge, state: State>, worklist: Worklist, Node, Set> -): Pair>, Boolean> { +): State> { // We will set this if we write to a variable val writtenDecl: Declaration? val currentNode = currentEdge.end - var expectedUpdate = true // isLoopPoint(currentNode) - val doubleState = state as DFGPassState val initializer = (currentNode as? VariableDeclaration)?.initializer if (initializer != null) { - expectedUpdate = true // A variable declaration with an initializer => The initializer flows to the // declaration. // We also wrote something to this variable declaration @@ -133,7 +130,6 @@ fun transfer( doubleState.pushToDeclarationsState(currentNode, PowersetLattice(setOf(currentNode))) } else if (currentNode is AssignExpression) { - expectedUpdate = true // It's an assignment which can have one or multiple things on the lhs and on the // rhs. The lhs could be a declaration or a reference (or multiple of these things). // The rhs can be anything. The rhs flows to the respective lhs. To identify the @@ -148,7 +144,6 @@ fun transfer( } } } else if (isIncOrDec(currentNode)) { - expectedUpdate = true // Increment or decrement => Add the prevWrite of the input to the input. After the // operation, the prevWrite of the input's variable is this node. val input = (currentNode as UnaryOperator).input as DeclaredReferenceExpression @@ -160,8 +155,6 @@ fun transfer( doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(input)) } } else if (isSimpleAssignment(currentNode)) { - expectedUpdate = true - // Only the lhs is the last write statement here and the variable which is written // to. writtenDecl = ((currentNode as BinaryOperator).lhs as DeclaredReferenceExpression).refersTo @@ -170,7 +163,6 @@ fun transfer( doubleState.declarationsState[writtenDecl] = PowersetLattice(setOf(currentNode.lhs)) } } else if (isCompoundAssignment(currentNode)) { - expectedUpdate = true // We write to the lhs, but it also serves as an input => We first get all previous // writes to the lhs and then add the flow from lhs and rhs to the current node. @@ -191,13 +183,11 @@ fun transfer( ) { // We can only find a change if there's a state for the variable doubleState.declarationsState[currentNode.refersTo]?.let { - expectedUpdate = true // We only read the variable => Get previous write which have been collected in // the other steps state.push(currentNode, it) } } else if (currentNode is ForEachStatement && currentNode.variable != null) { - expectedUpdate = true // The VariableDeclaration in the ForEachStatement doesn't have an initializer, so // the "normal" case won't work. We handle this case separately here... // This is what we write to the declaration @@ -256,7 +246,7 @@ fun transfer( doubleState.declarationsState[currentEdge.start] ) } - return Pair(state, expectedUpdate) + return state } /** From e67a75cae7f4f0bd402fad7919ace978d51125c3 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Wed, 14 Jun 2023 09:15:12 +0200 Subject: [PATCH 29/53] Small cleanup --- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index 19a6bbb532..faef4446f3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -93,19 +93,11 @@ open class ControlFlowSensitiveDFGPass(ctx: TranslationContext) : TranslationUni } } -fun isLoopPoint(node: Node) = - node is ForStatement || - node is WhileStatement || - node is ForEachStatement || - node is DoStatement || - node is GotoStatement || - node is ContinueStatement || - (node is Literal<*> && node in node.nextEOG) - /** - * Computes the previous write access of [node] if it is a [DeclaredReferenceExpression] or - * [ValueDeclaration] based on the given [state] (which maps all variables to its last write - * instruction). It also updates the [state] if [node] performs a write-operation to a variable. + * Computes the previous write access of [currentEdge].end if it is a [DeclaredReferenceExpression] + * or [ValueDeclaration] based on the given [state] (which maps all variables to its last write + * instruction). It also updates the [state] if [currentEdge].end performs a write-operation to a + * variable. * * It further determines unnecessary implicit return statement which are added by some frontends * even if every path reaching this point already contains a return statement. From 2f5ec728b3590ebaf3cfdfa1c412a852c52a20da Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 16 Jun 2023 11:03:44 +0200 Subject: [PATCH 30/53] Rename lattice to latticeelement --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 12 +++-- .../aisec/cpg/helpers/EOGWorklist.kt | 45 ++++++++++--------- .../cpg/passes/ControlDependenceGraphPass.kt | 12 ++--- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 17 ++++--- .../dfg/initializerListExpression.cpp | 9 ---- 5 files changed, 49 insertions(+), 46 deletions(-) delete mode 100644 cpg-core/src/test/resources/dfg/initializerListExpression.cpp diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index a3b01fdfcd..5d4f645999 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -193,14 +193,18 @@ private fun handleWhileStatement( remainingEdges.forEach { state.push(it, state[enteringEdge]) } } -/** Implements the [Lattice] over reachability properties: TOP | REACHABLE | UNREACHABLE | BOTTOM */ -class ReachabilityLattice(override val elements: Reachability) : Lattice(elements) { - override fun lub(other: Lattice?) = +/** + * Implements the [LatticeElement] over reachability properties: TOP | REACHABLE | UNREACHABLE | + * BOTTOM + */ +class ReachabilityLattice(override val elements: Reachability) : + LatticeElement(elements) { + override fun lub(other: LatticeElement?) = ReachabilityLattice(maxOf(this.elements, other?.elements ?: Reachability.BOTTOM)) override fun duplicate() = ReachabilityLattice(this.elements) - override fun compareTo(other: Lattice?) = + override fun compareTo(other: LatticeElement?) = this.elements.compareTo(other?.elements ?: Reachability.BOTTOM) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index 7083dd7fcf..acf2933646 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -40,27 +40,28 @@ import java.util.IdentityHashMap * Implementations of this class have to implement the comparator, the least upper bound of two * lattices. */ -abstract class Lattice(open val elements: T) : Comparable?> { +abstract class LatticeElement(open val elements: T) : Comparable?> { /** * Computes the least upper bound of this lattice and [other]. It returns a new object and does * not modify either of the objects. */ - abstract fun lub(other: Lattice?): Lattice + abstract fun lub(other: LatticeElement?): LatticeElement /** Duplicates the object, i.e., makes a deep copy. */ - abstract fun duplicate(): Lattice + abstract fun duplicate(): LatticeElement } /** - * Implements the [Lattice] over a set of nodes. The lattice itself is constructed by the powerset. + * Implements the [LatticeElement] for a lattice over a set of nodes. The lattice itself is + * constructed by the powerset. */ -class PowersetLattice(override val elements: Set) : Lattice>(elements) { - override fun lub(other: Lattice>?) = +class PowersetLattice(override val elements: Set) : LatticeElement>(elements) { + override fun lub(other: LatticeElement>?) = PowersetLattice((other?.elements ?: setOf()).union(this.elements)) override fun duplicate() = PowersetLattice(this.elements.toSet()) - override fun compareTo(other: Lattice>?): Int { + override fun compareTo(other: LatticeElement>?): Int { return if (this.elements.containsAll(other?.elements ?: setOf())) { if (this.elements.size > (other?.elements?.size ?: 0)) 1 else 0 } else { @@ -70,11 +71,12 @@ class PowersetLattice(override val elements: Set) : Lattice>(ele } /** - * Stores the current state. I.e., it maps [K] (e.g. a [Node] or [PropertyEdge]) to a [Lattice]. It - * provides some useful functions e.g. to check if the mapping has to be updated (e.g. because there - * are new nodes or because a new lattice is bigger than the old one). + * Stores the current state. I.e., it maps [K] (e.g. a [Node] or [PropertyEdge]) to a + * [LatticeElement]. It provides some useful functions e.g. to check if the mapping has to be + * updated (e.g. because there are new nodes or because a new lattice element is bigger than the old + * one). */ -open class State : IdentityHashMap>() { +open class State : IdentityHashMap>() { /** * It updates this state by adding all new nodes in [other] to `this` and by computing the least @@ -93,8 +95,8 @@ open class State : IdentityHashMap>() { /** * Checks if an update is necessary, i.e., if [other] contains nodes which are not present in - * `this` and if the lattice of a node in [other] "is bigger" than the respective lattice in - * `this`. It does not modify anything. + * `this` and if the lattice element of a node in [other] "is bigger" than the respective + * lattice element in `this`. It does not modify anything. */ open fun needsUpdate(other: State): Boolean { var update = false @@ -114,23 +116,24 @@ open class State : IdentityHashMap>() { } /** - * Adds a new mapping from [newNode] to (a copy of) [newLattice] to this object if [newNode] - * does not exist in this state yet. If it already exists, it computes the least upper bound of - * [newLattice] and the current one for [newNode]. It returns if the state has changed. + * Adds a new mapping from [newNode] to (a copy of) [newLatticeElement] to this object if + * [newNode] does not exist in this state yet. If it already exists, it computes the least upper + * bound of [newLatticeElement] and the current one for [newNode]. It returns if the state has + * changed. */ - open fun push(newNode: K, newLattice: Lattice?): Boolean { - if (newLattice == null) { + open fun push(newNode: K, newLatticeElement: LatticeElement?): Boolean { + if (newLatticeElement == null) { return false } - if (newNode in this && this[newNode]?.let { it >= newLattice } == true) { + if (newNode in this && this[newNode]?.let { it >= newLatticeElement } == true) { // newLattice is "smaller" than the currently stored one. We don't add it anything. return false } else if (newNode in this) { // newLattice is "bigger" than the currently stored one. We update it to the least // upper bound - this[newNode] = newLattice.lub(this[newNode]) + this[newNode] = newLatticeElement.lub(this[newNode]) } else { - this[newNode] = newLattice.duplicate() + this[newNode] = newLatticeElement.duplicate() } return true } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 15a972ea79..1bfbe1913a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -219,12 +219,14 @@ private fun PropertyEdge.isConditionalBranch(): Boolean { } /** - * Implements the [Lattice] over a set of nodes and their set of "nextEOG" nodes which reach this - * node. + * Implements the [LatticeElement] over a set of nodes and their set of "nextEOG" nodes which reach + * this node. */ class PrevEOGLattice(override val elements: Map>) : - Lattice>>(elements) { - override fun lub(other: Lattice>>?): Lattice>> { + LatticeElement>>(elements) { + override fun lub( + other: LatticeElement>>? + ): LatticeElement>> { val newMap = (other?.elements ?: mapOf()).mapValues { (_, v) -> v.toMutableSet() }.toMutableMap() for ((key, value) in this.elements) { @@ -235,7 +237,7 @@ class PrevEOGLattice(override val elements: Map>) : override fun duplicate() = PrevEOGLattice(this.elements.toMap()) - override fun compareTo(other: Lattice>>?): Int { + override fun compareTo(other: LatticeElement>>?): Int { return if ( this.elements.keys.containsAll(other?.elements?.keys ?: setOf()) && this.elements.all { (k, v) -> v.containsAll(other?.elements?.get(k) ?: setOf()) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index faef4446f3..d9e3cdc837 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -286,9 +286,9 @@ private fun removeUnreachableImplicitReturnStatement( * ReturnStatements. */ class DFGPassState( - /** A mapping of a [Node] to its [Lattice]. */ + /** A mapping of a [Node] to its [LatticeElement]. */ var generalState: State = State(), - /** A mapping of [Declaration] to its [Lattice]. */ + /** A mapping of [Declaration] to its [LatticeElement]. */ var declarationsState: State = State(), /** The [returnStatements] which are reachable. */ var returnStatements: State = State() @@ -317,12 +317,15 @@ class DFGPassState( } } - override fun push(newNode: Node, newLattice: Lattice?): Boolean { - return generalState.push(newNode, newLattice) + override fun push(newNode: Node, newLatticeElement: LatticeElement?): Boolean { + return generalState.push(newNode, newLatticeElement) } - /** Pushes the [newNode] and its [newLattice] to the [declarationsState]. */ - fun pushToDeclarationsState(newNode: Declaration, newLattice: Lattice?): Boolean { - return declarationsState.push(newNode, newLattice) + /** Pushes the [newNode] and its [newLatticeElement] to the [declarationsState]. */ + fun pushToDeclarationsState( + newNode: Declaration, + newLatticeElement: LatticeElement? + ): Boolean { + return declarationsState.push(newNode, newLatticeElement) } } diff --git a/cpg-core/src/test/resources/dfg/initializerListExpression.cpp b/cpg-core/src/test/resources/dfg/initializerListExpression.cpp deleted file mode 100644 index 7fbc5e9cff..0000000000 --- a/cpg-core/src/test/resources/dfg/initializerListExpression.cpp +++ /dev/null @@ -1,9 +0,0 @@ -int foo() { - return 0; -} - -int main() { - int i{foo()}; - - return i; -} \ No newline at end of file From faa7d700a95ea1325c858864dc52d07cf6fe8e9e Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 16 Jun 2023 13:46:53 +0200 Subject: [PATCH 31/53] Remove nullable properties --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 10 ++++--- .../aisec/cpg/helpers/EOGWorklist.kt | 28 ++++++++++++------- .../cpg/passes/ControlDependenceGraphPass.kt | 19 ++++++------- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index 5d4f645999..f90f3a4739 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -199,13 +199,15 @@ private fun handleWhileStatement( */ class ReachabilityLattice(override val elements: Reachability) : LatticeElement(elements) { - override fun lub(other: LatticeElement?) = - ReachabilityLattice(maxOf(this.elements, other?.elements ?: Reachability.BOTTOM)) + override fun lub(other: LatticeElement) = + ReachabilityLattice(maxOf(this.elements, other.elements)) override fun duplicate() = ReachabilityLattice(this.elements) - override fun compareTo(other: LatticeElement?) = - this.elements.compareTo(other?.elements ?: Reachability.BOTTOM) + override val BOT by lazy { ReachabilityLattice(Reachability.BOTTOM) } + + override fun compareTo(other: LatticeElement) = + this.elements.compareTo(other.elements) } /** The ordering will be as follows: BOTTOM (no information) < UNREACHABLE < REACHABLE < TOP */ diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index acf2933646..478077b7f7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -40,15 +40,17 @@ import java.util.IdentityHashMap * Implementations of this class have to implement the comparator, the least upper bound of two * lattices. */ -abstract class LatticeElement(open val elements: T) : Comparable?> { +abstract class LatticeElement(open val elements: T) : Comparable> { /** * Computes the least upper bound of this lattice and [other]. It returns a new object and does * not modify either of the objects. */ - abstract fun lub(other: LatticeElement?): LatticeElement + abstract fun lub(other: LatticeElement): LatticeElement /** Duplicates the object, i.e., makes a deep copy. */ abstract fun duplicate(): LatticeElement + + abstract val BOT: LatticeElement } /** @@ -56,14 +58,16 @@ abstract class LatticeElement(open val elements: T) : Comparable) : LatticeElement>(elements) { - override fun lub(other: LatticeElement>?) = - PowersetLattice((other?.elements ?: setOf()).union(this.elements)) + override fun lub(other: LatticeElement>) = + PowersetLattice((other.elements).union(this.elements)) override fun duplicate() = PowersetLattice(this.elements.toSet()) - override fun compareTo(other: LatticeElement>?): Int { - return if (this.elements.containsAll(other?.elements ?: setOf())) { - if (this.elements.size > (other?.elements?.size ?: 0)) 1 else 0 + override val BOT by lazy { PowersetLattice(setOf()) } + + override fun compareTo(other: LatticeElement>): Int { + return if (this.elements.containsAll(other.elements)) { + if (this.elements.size > (other.elements.size)) 1 else 0 } else { -1 } @@ -101,7 +105,10 @@ open class State : IdentityHashMap>() { open fun needsUpdate(other: State): Boolean { var update = false for ((node, newLattice) in other) { - update = update || node !in this || newLattice > this[node] + update = + update || + node !in this || + newLattice > this.computeIfAbsent(node) { newLattice.BOT } } return update } @@ -131,7 +138,8 @@ open class State : IdentityHashMap>() { } else if (newNode in this) { // newLattice is "bigger" than the currently stored one. We update it to the least // upper bound - this[newNode] = newLatticeElement.lub(this[newNode]) + this[newNode] = + newLatticeElement.lub(this.computeIfAbsent(newNode) { newLatticeElement.BOT }) } else { this[newNode] = newLatticeElement.duplicate() } @@ -221,7 +229,7 @@ class Worklist() { state?.lub(v) } - return state ?: State() + return state } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 1bfbe1913a..69dba7340f 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -224,11 +224,12 @@ private fun PropertyEdge.isConditionalBranch(): Boolean { */ class PrevEOGLattice(override val elements: Map>) : LatticeElement>>(elements) { + override val BOT by lazy { PrevEOGLattice(mapOf()) } + override fun lub( - other: LatticeElement>>? + other: LatticeElement>> ): LatticeElement>> { - val newMap = - (other?.elements ?: mapOf()).mapValues { (_, v) -> v.toMutableSet() }.toMutableMap() + val newMap = (other.elements).mapValues { (_, v) -> v.toMutableSet() }.toMutableMap() for ((key, value) in this.elements) { newMap.computeIfAbsent(key, ::mutableSetOf).addAll(value) } @@ -237,16 +238,14 @@ class PrevEOGLattice(override val elements: Map>) : override fun duplicate() = PrevEOGLattice(this.elements.toMap()) - override fun compareTo(other: LatticeElement>>?): Int { + override fun compareTo(other: LatticeElement>>): Int { return if ( - this.elements.keys.containsAll(other?.elements?.keys ?: setOf()) && - this.elements.all { (k, v) -> v.containsAll(other?.elements?.get(k) ?: setOf()) } + this.elements.keys.containsAll(other.elements.keys) && + this.elements.all { (k, v) -> v.containsAll(other.elements[k] ?: setOf()) } ) { if ( - this.elements.keys.size > (other?.elements?.keys?.size ?: 0) || - this.elements.any { (k, v) -> - v.size > (other?.elements?.get(k) ?: setOf()).size - } + this.elements.keys.size > (other.elements.keys.size) || + this.elements.any { (k, v) -> v.size > (other.elements[k] ?: setOf()).size } ) 1 else 0 From 9455a71b5a28077cc83a342306e8862072197c4b Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 16 Jun 2023 13:54:05 +0200 Subject: [PATCH 32/53] Nicer code --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 2 -- .../aisec/cpg/helpers/EOGWorklist.kt | 18 ++++++------------ .../cpg/passes/ControlDependenceGraphPass.kt | 1 - 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index f90f3a4739..5cb0609f1b 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -204,8 +204,6 @@ class ReachabilityLattice(override val elements: Reachability) : override fun duplicate() = ReachabilityLattice(this.elements) - override val BOT by lazy { ReachabilityLattice(Reachability.BOTTOM) } - override fun compareTo(other: LatticeElement) = this.elements.compareTo(other.elements) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt index 478077b7f7..d7238dc397 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt @@ -49,8 +49,6 @@ abstract class LatticeElement(open val elements: T) : Comparable - - abstract val BOT: LatticeElement } /** @@ -63,8 +61,6 @@ class PowersetLattice(override val elements: Set) : LatticeElement>): Int { return if (this.elements.containsAll(other.elements)) { if (this.elements.size > (other.elements.size)) 1 else 0 @@ -105,10 +101,8 @@ open class State : IdentityHashMap>() { open fun needsUpdate(other: State): Boolean { var update = false for ((node, newLattice) in other) { - update = - update || - node !in this || - newLattice > this.computeIfAbsent(node) { newLattice.BOT } + val current = this[node] + update = update || current == null || newLattice > current } return update } @@ -132,14 +126,14 @@ open class State : IdentityHashMap>() { if (newLatticeElement == null) { return false } - if (newNode in this && this[newNode]?.let { it >= newLatticeElement } == true) { + val current = this[newNode] + if (current != null && current >= newLatticeElement) { // newLattice is "smaller" than the currently stored one. We don't add it anything. return false - } else if (newNode in this) { + } else if (current != null) { // newLattice is "bigger" than the currently stored one. We update it to the least // upper bound - this[newNode] = - newLatticeElement.lub(this.computeIfAbsent(newNode) { newLatticeElement.BOT }) + this[newNode] = newLatticeElement.lub(current) } else { this[newNode] = newLatticeElement.duplicate() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt index 69dba7340f..6ab50070c7 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlDependenceGraphPass.kt @@ -224,7 +224,6 @@ private fun PropertyEdge.isConditionalBranch(): Boolean { */ class PrevEOGLattice(override val elements: Map>) : LatticeElement>>(elements) { - override val BOT by lazy { PrevEOGLattice(mapOf()) } override fun lub( other: LatticeElement>> From 39b0bbdb332441d869ff7903e8cdc3f8788fad78 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 16 Jun 2023 14:48:49 +0200 Subject: [PATCH 33/53] Documentation --- .../cpg/passes/ControlFlowSensitiveDFGPass.kt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt index d9e3cdc837..c5405db205 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ControlFlowSensitiveDFGPass.kt @@ -228,7 +228,7 @@ fun transfer( } else if (currentNode is FunctionDeclaration) { // We have to add the parameters currentNode.parameters.forEach { - doubleState.declarationsState.push(it, PowersetLattice(setOf(it))) + doubleState.pushToDeclarationsState(it, PowersetLattice(setOf(it))) } } else if (currentNode is ReturnStatement) { doubleState.returnStatements.push(currentNode, PowersetLattice(setOf(currentNode))) @@ -286,9 +286,18 @@ private fun removeUnreachableImplicitReturnStatement( * ReturnStatements. */ class DFGPassState( - /** A mapping of a [Node] to its [LatticeElement]. */ + /** + * A mapping of a [Node] to its [LatticeElement]. The keys of this state will later get the DFG + * edges from the value! + */ var generalState: State = State(), - /** A mapping of [Declaration] to its [LatticeElement]. */ + /** + * It's main purpose is to store the most recent mapping of a [Declaration] to its + * [LatticeElement]. However, it is also used to figure out if we have to continue with the + * iteration (something in the declarationState has changed) which is why we store all nodes + * here. However, since we never use them except from determining if we changed something, it + * won't affect the result. + */ var declarationsState: State = State(), /** The [returnStatements] which are reachable. */ var returnStatements: State = State() @@ -297,6 +306,10 @@ class DFGPassState( return DFGPassState(generalState.duplicate(), declarationsState.duplicate()) } + override fun get(key: Node?): LatticeElement? { + return generalState[key] ?: declarationsState[key] + } + override fun lub(other: State): Pair, Boolean> { return if (other is DFGPassState) { val (_, generalUpdate) = generalState.lub(other.generalState) From 94496dd37ca5141b20e03c88d73548b04b05f331 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 16 Jun 2023 16:27:27 +0200 Subject: [PATCH 34/53] first draft for generating pdg property edges --- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 16 ++++++++++++++++ .../aisec/cpg/graph/edge/PropertyEdge.kt | 17 +++++++++++++++++ 2 files changed, 33 insertions(+) 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 f1410309e7..4bc466dd42 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 @@ -40,6 +40,7 @@ import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeCombinationDelegate import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope @@ -54,6 +55,7 @@ import de.fraunhofer.aisec.cpg.passes.FilenameMapper import de.fraunhofer.aisec.cpg.processing.IVisitable import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* +import kotlin.collections.ArrayList import org.apache.commons.lang3.builder.ToStringBuilder import org.apache.commons.lang3.builder.ToStringStyle import org.neo4j.ogm.annotation.* @@ -196,6 +198,20 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider @Relationship(value = "DFG") var nextDFG: MutableSet = HashSet() + /** */ + @delegate:Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING) + val nextPDGEdges: List> by + PropertyEdgeCombinationDelegate(listOf(nextCDGEdges), listOf(nextDFG)) + + var nextPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) + + /** */ + @delegate:Relationship(value = "PDG", direction = Relationship.Direction.INCOMING) + val prevPDGEdges: List> by + PropertyEdgeCombinationDelegate(listOf(prevCDGEdges), listOf(prevDFG)) + + var prevPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) + var typedefs: MutableSet = HashSet() /** diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index 1b5e59d4f2..c8ffef12c0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -373,3 +373,20 @@ class PropertyEdgeDelegate( } } } + +/** + * This class can be used to implement + * [delegated properties](https://kotlinlang.org/docs/delegated-properties.html) in [Node] classes. + * It combines the given [edges] and [nodes] into a new list of [PropertyEdge]s. + */ +class PropertyEdgeCombinationDelegate( + val edges: Collection>>, + val nodes: Collection> +) { + operator fun getValue(thisRef: S, property: KProperty<*>): List> { + return edges.flatten().map { PropertyEdge(it) } + + nodes.flatMap { + PropertyEdge.transformIntoOutgoingPropertyEdgeList(it.toList(), thisRef) + } + } +} From 0279df32a6d8a0bcd23bd2d32b43875c5714394b Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 19 Jun 2023 19:55:43 +0200 Subject: [PATCH 35/53] change delegate constructor parameter to KProperty1 for consistency --- .../main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt | 6 +++--- .../fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt | 11 +++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 4bc466dd42..af1cfc3006 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 @@ -39,8 +39,8 @@ import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeCombinationDelegate +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope import de.fraunhofer.aisec.cpg.graph.scopes.Scope @@ -201,14 +201,14 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider /** */ @delegate:Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING) val nextPDGEdges: List> by - PropertyEdgeCombinationDelegate(listOf(nextCDGEdges), listOf(nextDFG)) + PropertyEdgeCombinationDelegate(listOf(Node::nextCDGEdges), listOf(Node::nextDFG)) var nextPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) /** */ @delegate:Relationship(value = "PDG", direction = Relationship.Direction.INCOMING) val prevPDGEdges: List> by - PropertyEdgeCombinationDelegate(listOf(prevCDGEdges), listOf(prevDFG)) + PropertyEdgeCombinationDelegate(listOf(Node::prevCDGEdges), listOf(Node::prevDFG)) var prevPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index c8ffef12c0..fa0c3857b5 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -380,13 +380,16 @@ class PropertyEdgeDelegate( * It combines the given [edges] and [nodes] into a new list of [PropertyEdge]s. */ class PropertyEdgeCombinationDelegate( - val edges: Collection>>, - val nodes: Collection> + val edges: Collection>>>, + val nodes: Collection>> ) { operator fun getValue(thisRef: S, property: KProperty<*>): List> { - return edges.flatten().map { PropertyEdge(it) } + + return edges.flatMap { it.get(thisRef) }.map { PropertyEdge(it) } + nodes.flatMap { - PropertyEdge.transformIntoOutgoingPropertyEdgeList(it.toList(), thisRef) + PropertyEdge.transformIntoOutgoingPropertyEdgeList( + it.get(thisRef).toList(), + thisRef + ) } } } From 19e6b02fa7159da15a15fbb159e98443ea06fe7b Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 19 Jun 2023 20:07:47 +0200 Subject: [PATCH 36/53] change PDGEdges properties from using delegates to custom getters --- .../kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index af1cfc3006..838bdbec11 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 @@ -199,16 +199,20 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider var nextDFG: MutableSet = HashSet() /** */ - @delegate:Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING) - val nextPDGEdges: List> by - PropertyEdgeCombinationDelegate(listOf(Node::nextCDGEdges), listOf(Node::nextDFG)) + @Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING) + var nextPDGEdges: List> = listOf() + get() = + nextCDGEdges.map { PropertyEdge(it) } + + PropertyEdge.transformIntoOutgoingPropertyEdgeList(nextDFG.toList(), this) var nextPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) /** */ - @delegate:Relationship(value = "PDG", direction = Relationship.Direction.INCOMING) - val prevPDGEdges: List> by - PropertyEdgeCombinationDelegate(listOf(Node::prevCDGEdges), listOf(Node::prevDFG)) + @Relationship(value = "PDG", direction = Relationship.Direction.INCOMING) + var prevPDGEdges: List> = listOf() + get() = + prevCDGEdges.map { PropertyEdge(it) } + + PropertyEdge.transformIntoOutgoingPropertyEdgeList(prevDFG.toList(), this) var prevPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) From 339bbecf561dba66cc15e3eb9b3810ef6aaabcb0 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 22 Jun 2023 12:08:37 +0200 Subject: [PATCH 37/53] add EventListener to set pdg edges before neo4j serialization --- .../fraunhofer/aisec/cpg_vis_neo4j/Application.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index cb615852e9..198eba6baf 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg_vis_neo4j import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.* @@ -301,6 +302,7 @@ class Application : Callable { "de.fraunhofer.aisec.cpg.frontends" ) sessionFactory.register(AstChildrenEventListener()) + sessionFactory.register(PDGEventListener()) session = sessionFactory.openSession() } catch (ex: ConnectionException) { @@ -481,6 +483,18 @@ class AstChildrenEventListener : EventListenerAdapter() { } } +class PDGEventListener : EventListenerAdapter() { + override fun onPreSave(event: Event?) { + val node = event?.`object` as? Node ?: return + val prevPDGEdges = node.prevCDGEdges.map { PropertyEdge(it) } + + PropertyEdge.transformIntoOutgoingPropertyEdgeList(node.prevDFG.toList(), node) + node.prevPDGEdges = prevPDGEdges + val nextPDGEdges = node.nextCDGEdges.map { PropertyEdge(it) } + + PropertyEdge.transformIntoOutgoingPropertyEdgeList(node.nextDFG.toList(), node) + node.nextPDGEdges = nextPDGEdges + } +} + /** * Starts a command line application of the cpg-vis-neo4j. * From 998d1b3d7639eeae41e24ebc707feb09cecfa5c9 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 23 Jun 2023 17:04:10 +0200 Subject: [PATCH 38/53] change to adding the PDG edges through a new Pass --- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 43 ++++++++----- .../cpg/passes/ProgramDependencyGraphPass.kt | 62 +++++++++++++++++++ .../aisec/cpg_vis_neo4j/Application.kt | 14 ----- 3 files changed, 89 insertions(+), 30 deletions(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt 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 838bdbec11..5ca53e9543 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 @@ -39,7 +39,6 @@ import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeCombinationDelegate import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdgeDelegate import de.fraunhofer.aisec.cpg.graph.scopes.GlobalScope import de.fraunhofer.aisec.cpg.graph.scopes.RecordScope @@ -47,11 +46,7 @@ import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.neo4j.LocationConverter import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter -import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass -import de.fraunhofer.aisec.cpg.passes.ControlFlowSensitiveDFGPass -import de.fraunhofer.aisec.cpg.passes.DFGPass -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass -import de.fraunhofer.aisec.cpg.passes.FilenameMapper +import de.fraunhofer.aisec.cpg.passes.* import de.fraunhofer.aisec.cpg.processing.IVisitable import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* @@ -198,22 +193,22 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider @Relationship(value = "DFG") var nextDFG: MutableSet = HashSet() - /** */ + /** Outgoing Program Dependency Edges. */ + @PopulatedByPass(ProgramDependencyGraphPass::class) @Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING) - var nextPDGEdges: List> = listOf() - get() = - nextCDGEdges.map { PropertyEdge(it) } + - PropertyEdge.transformIntoOutgoingPropertyEdgeList(nextDFG.toList(), this) + var nextPDGEdges: MutableList> = mutableListOf() + protected set + /** Virtual property for accessing the children of the Program Dependency Graph (PDG). */ var nextPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) - /** */ + /** Incoming Program Dependency Edges. */ + @PopulatedByPass(ProgramDependencyGraphPass::class) @Relationship(value = "PDG", direction = Relationship.Direction.INCOMING) - var prevPDGEdges: List> = listOf() - get() = - prevCDGEdges.map { PropertyEdge(it) } + - PropertyEdge.transformIntoOutgoingPropertyEdgeList(prevDFG.toList(), this) + var prevPDGEdges: MutableList> = mutableListOf() + protected set + /** Virtual property for accessing the parents of the Program Dependency Graph (PDG). */ var prevPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) var typedefs: MutableSet = HashSet() @@ -292,6 +287,22 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider prev.forEach { it.nextDFG.add(this) } } + fun addAllPrevPDG(prev: Collection) { + addAllPrevPDGEdges(prev.map { PropertyEdge(this, it) }) + } + + fun addAllPrevPDGEdges(prev: Collection>) { + prevPDGEdges.addAll(prev.map { PropertyEdge(it) }) + } + + fun addAlllNextPDG(next: Collection) { + addAllNextPDGEdges(next.map { PropertyEdge(this, it) }) + } + + fun addAllNextPDGEdges(next: Collection>) { + nextPDGEdges.addAll(next.map { PropertyEdge(it) }) + } + fun removePrevDFG(prev: Node?) { if (prev != null) { prevDFG.remove(prev) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt new file mode 100644 index 0000000000..f93b6ee408 --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023, 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.passes + +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.graph.* +import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.passes.order.DependsOn +import de.fraunhofer.aisec.cpg.processing.IVisitor +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy + +@DependsOn(ControlDependenceGraphPass::class) +@DependsOn(DFGPass::class) +@DependsOn(ControlFlowSensitiveDFGPass::class, softDependency = true) +class ProgramDependencyGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { + private val pdgSetter = + object : IVisitor() { + override fun visit(t: Node) { + t.addAllPrevPDG(t.prevDFG) + t.addAllNextPDGEdges(t.prevCDGEdges) + t.addAlllNextPDG(t.nextDFG) + t.addAllNextPDGEdges(t.nextCDGEdges) + } + } + + override fun accept(tu: TranslationUnitDeclaration) { + tu.statements.forEach(::handle) + tu.namespaces.forEach(::handle) + tu.declarations.forEach(::handle) + } + + override fun cleanup() { + // Nothing to do + } + + private fun handle(node: Node) { + node.accept(Strategy::AST_FORWARD, pdgSetter) + } +} diff --git a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt index 198eba6baf..cb615852e9 100644 --- a/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt +++ b/cpg-neo4j/src/main/kotlin/de/fraunhofer/aisec/cpg_vis_neo4j/Application.kt @@ -28,7 +28,6 @@ package de.fraunhofer.aisec.cpg_vis_neo4j import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.CompilationDatabase.Companion.fromFile import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.helpers.Benchmark import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.passes.* @@ -302,7 +301,6 @@ class Application : Callable { "de.fraunhofer.aisec.cpg.frontends" ) sessionFactory.register(AstChildrenEventListener()) - sessionFactory.register(PDGEventListener()) session = sessionFactory.openSession() } catch (ex: ConnectionException) { @@ -483,18 +481,6 @@ class AstChildrenEventListener : EventListenerAdapter() { } } -class PDGEventListener : EventListenerAdapter() { - override fun onPreSave(event: Event?) { - val node = event?.`object` as? Node ?: return - val prevPDGEdges = node.prevCDGEdges.map { PropertyEdge(it) } + - PropertyEdge.transformIntoOutgoingPropertyEdgeList(node.prevDFG.toList(), node) - node.prevPDGEdges = prevPDGEdges - val nextPDGEdges = node.nextCDGEdges.map { PropertyEdge(it) } + - PropertyEdge.transformIntoOutgoingPropertyEdgeList(node.nextDFG.toList(), node) - node.nextPDGEdges = nextPDGEdges - } -} - /** * Starts a command line application of the cpg-vis-neo4j. * From cc9dd650d7a9fd559db688ceb4670374bacc7e5d Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 26 Jun 2023 18:21:47 +0200 Subject: [PATCH 39/53] fix typos --- .../src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt | 2 +- .../fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 5ca53e9543..150d5437bb 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 @@ -295,7 +295,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider prevPDGEdges.addAll(prev.map { PropertyEdge(it) }) } - fun addAlllNextPDG(next: Collection) { + fun addAllNextPDG(next: Collection) { addAllNextPDGEdges(next.map { PropertyEdge(this, it) }) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt index f93b6ee408..dd116365c1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt @@ -40,8 +40,8 @@ class ProgramDependencyGraphPass(ctx: TranslationContext) : TranslationUnitPass( object : IVisitor() { override fun visit(t: Node) { t.addAllPrevPDG(t.prevDFG) - t.addAllNextPDGEdges(t.prevCDGEdges) - t.addAlllNextPDG(t.nextDFG) + t.addAllPrevPDGEdges(t.prevCDGEdges) + t.addAllNextPDG(t.nextDFG) t.addAllNextPDGEdges(t.nextCDGEdges) } } From d55fbc49748645ecf5bc0636dbd190d2e0168879 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 26 Jun 2023 18:22:26 +0200 Subject: [PATCH 40/53] add test for the ProgramDependenceGraphPass --- .../passes/ProgramDependenceGraphPassTest.kt | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt new file mode 100644 index 0000000000..e1ff6ad3ef --- /dev/null +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2023, 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.passes + +import de.fraunhofer.aisec.cpg.ScopeManager +import de.fraunhofer.aisec.cpg.TranslationConfiguration +import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.frontends.TestLanguage +import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend +import de.fraunhofer.aisec.cpg.graph.Node +import de.fraunhofer.aisec.cpg.graph.TypeManager +import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge +import de.fraunhofer.aisec.cpg.graph.functions +import de.fraunhofer.aisec.cpg.graph.get +import de.fraunhofer.aisec.cpg.processing.IVisitor +import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import kotlin.test.assertContentEquals +import kotlin.test.assertNotNull +import org.junit.jupiter.api.Test + +class ProgramDependenceGraphPassTest { + + @Test + fun `test pdg of if statement`() { + val result = getIfTest() + assertNotNull(result) + val main = result.functions["main"] + assertNotNull(main) + + main.accept( + Strategy::AST_FORWARD, + object : IVisitor() { + override fun visit(t: Node) { + val expectedPrevEdges = + (t.prevCDGEdges + t.prevDFG.map { PropertyEdge(t, it) }).sortedBy { + it.hashCode() + } + assertContentEquals( + expectedPrevEdges, + t.prevPDGEdges.sortedBy { it.hashCode() }, + "prevPDGEdges did not contain all prevCDGEdges and edges to all prevDFG" + ) + + val expectedNextEdges = + (t.nextCDGEdges + t.nextDFG.map { PropertyEdge(t, it) }).sortedBy { + it.hashCode() + } + assertContentEquals( + expectedNextEdges, + t.nextPDGEdges.sortedBy { it.hashCode() }, + "prevPDGEdges did not contain all nextCDGEdges and edges to all nextDFG" + ) + } + } + ) + } + + companion object { + fun testFrontend(config: TranslationConfiguration): TestLanguageFrontend { + val ctx = TranslationContext(config, ScopeManager(), TypeManager()) + val language = config.languages.filterIsInstance().first() + return TestLanguageFrontend(language.namespaceDelimiter, language, ctx) + } + + fun getIfTest() = + testFrontend( + TranslationConfiguration.builder() + .registerLanguage(TestLanguage("::")) + .defaultPasses() + .registerPass() + .registerPass() + .build() + ) + .build { + translationResult { + translationUnit("if.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("i", t("int")) { call("rand") } } + ifStmt { + condition { ref("i") lt literal(0, t("int")) } + thenStmt { + ref("i") assign (ref("i") * literal(-1, t("int"))) + } + } + returnStmt { ref("i") } + } + } + } + } + } + } +} From 25f7995487208459822e6a0821204f41f0fec196 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Thu, 29 Jun 2023 11:04:14 +0200 Subject: [PATCH 41/53] add more tests --- .../passes/ProgramDependenceGraphPassTest.kt | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt index e1ff6ad3ef..954f27c74a 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.ScopeManager import de.fraunhofer.aisec.cpg.TranslationConfiguration import de.fraunhofer.aisec.cpg.TranslationContext +import de.fraunhofer.aisec.cpg.TranslationResult import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node @@ -38,15 +39,18 @@ import de.fraunhofer.aisec.cpg.graph.functions import de.fraunhofer.aisec.cpg.graph.get import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import java.util.stream.Stream import kotlin.test.assertContentEquals import kotlin.test.assertNotNull -import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource class ProgramDependenceGraphPassTest { - @Test - fun `test pdg of if statement`() { - val result = getIfTest() + @ParameterizedTest(name = "test if pdg of {1} is equal to the union of cdg and dfg") + @MethodSource("provideTranslationResultForPDGTest") + fun `test if pdg is equal to union of cdg and dfg`(result: TranslationResult, name: String) { assertNotNull(result) val main = result.functions["main"] assertNotNull(main) @@ -86,7 +90,14 @@ class ProgramDependenceGraphPassTest { return TestLanguageFrontend(language.namespaceDelimiter, language, ctx) } - fun getIfTest() = + @JvmStatic + fun provideTranslationResultForPDGTest() = + Stream.of( + Arguments.of(getIfTest(), "if statement"), + Arguments.of(getWhileLoopTest(), "while loop") + ) + + private fun getIfTest() = testFrontend( TranslationConfiguration.builder() .registerLanguage(TestLanguage("::")) @@ -114,5 +125,36 @@ class ProgramDependenceGraphPassTest { } } } + + private fun getWhileLoopTest() = + testFrontend( + TranslationConfiguration.builder() + .registerLanguage(TestLanguage("::")) + .defaultPasses() + .registerPass() + .registerPass() + .build() + ) + .build { + translationResult { + translationUnit("loop.cpp") { + // The main method + function("main", t("int")) { + body { + declare { variable("i", t("int")) { call("rand") } } + whileStmt { + whileCondition { ref("i") gt literal(0, t("int")) } + loopBody { + call("printf") { literal("#", t("string")) } + ref("i").dec() + } + } + call("printf") { literal("\n", t("string")) } + returnStmt { literal(0, t("int")) } + } + } + } + } + } } } From 7423ab5284f3f949c9cd350d25bd94474212ea07 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 30 Jun 2023 14:58:33 +0200 Subject: [PATCH 42/53] add documentation --- .../fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt index dd116365c1..86bd80e6c2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt @@ -38,6 +38,10 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy class ProgramDependencyGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { private val pdgSetter = object : IVisitor() { + /** + * Collects the data and control dependency edges of a node and adds them to the + * program dependency edges + */ override fun visit(t: Node) { t.addAllPrevPDG(t.prevDFG) t.addAllPrevPDGEdges(t.prevCDGEdges) From 777da64d3558e0107e4e5b4488dc00615b632a2c Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 30 Jun 2023 15:06:53 +0200 Subject: [PATCH 43/53] fix format violation --- .../fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt index 86bd80e6c2..8d35f249e2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt @@ -39,8 +39,8 @@ class ProgramDependencyGraphPass(ctx: TranslationContext) : TranslationUnitPass( private val pdgSetter = object : IVisitor() { /** - * Collects the data and control dependency edges of a node and adds them to the - * program dependency edges + * Collects the data and control dependency edges of a node and adds them to the program + * dependency edges */ override fun visit(t: Node) { t.addAllPrevPDG(t.prevDFG) From 5a543fb73d123c4e73d9d49a52cd501a6e170822 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 30 Jun 2023 15:24:32 +0200 Subject: [PATCH 44/53] remove unused delegate class --- .../aisec/cpg/graph/edge/PropertyEdge.kt | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index fa0c3857b5..1b5e59d4f2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -373,23 +373,3 @@ class PropertyEdgeDelegate( } } } - -/** - * This class can be used to implement - * [delegated properties](https://kotlinlang.org/docs/delegated-properties.html) in [Node] classes. - * It combines the given [edges] and [nodes] into a new list of [PropertyEdge]s. - */ -class PropertyEdgeCombinationDelegate( - val edges: Collection>>>, - val nodes: Collection>> -) { - operator fun getValue(thisRef: S, property: KProperty<*>): List> { - return edges.flatMap { it.get(thisRef) }.map { PropertyEdge(it) } + - nodes.flatMap { - PropertyEdge.transformIntoOutgoingPropertyEdgeList( - it.get(thisRef).toList(), - thisRef - ) - } - } -} From 3e474413a33639c60a706491c543f5eb349aa4d2 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 3 Jul 2023 19:08:58 +0200 Subject: [PATCH 45/53] rename pass to ProgramDependenceGraphPass and add documentation --- .../kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt | 12 ++++++------ ...ncyGraphPass.kt => ProgramDependenceGraphPass.kt} | 10 +++++++--- .../cpg/passes/ProgramDependenceGraphPassTest.kt | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) rename cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/{ProgramDependencyGraphPass.kt => ProgramDependenceGraphPass.kt} (88%) 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 150d5437bb..2b44118e42 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 @@ -193,22 +193,22 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider @Relationship(value = "DFG") var nextDFG: MutableSet = HashSet() - /** Outgoing Program Dependency Edges. */ - @PopulatedByPass(ProgramDependencyGraphPass::class) + /** Outgoing Program Dependence Edges. */ + @PopulatedByPass(ProgramDependenceGraphPass::class) @Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING) var nextPDGEdges: MutableList> = mutableListOf() protected set - /** Virtual property for accessing the children of the Program Dependency Graph (PDG). */ + /** Virtual property for accessing the children of the Program Dependence Graph (PDG). */ var nextPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) - /** Incoming Program Dependency Edges. */ - @PopulatedByPass(ProgramDependencyGraphPass::class) + /** Incoming Program Dependence Edges. */ + @PopulatedByPass(ProgramDependenceGraphPass::class) @Relationship(value = "PDG", direction = Relationship.Direction.INCOMING) var prevPDGEdges: MutableList> = mutableListOf() protected set - /** Virtual property for accessing the parents of the Program Dependency Graph (PDG). */ + /** Virtual property for accessing the parents of the Program Dependence Graph (PDG). */ var prevPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) var typedefs: MutableSet = HashSet() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt similarity index 88% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt rename to cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt index 8d35f249e2..6978e60681 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependencyGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt @@ -32,15 +32,19 @@ import de.fraunhofer.aisec.cpg.passes.order.DependsOn import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +/** + * This pass collects the dependence information of each node into a Program Dependence Graph (PDG) + * by traversing through the AST. + */ @DependsOn(ControlDependenceGraphPass::class) @DependsOn(DFGPass::class) @DependsOn(ControlFlowSensitiveDFGPass::class, softDependency = true) -class ProgramDependencyGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { +class ProgramDependenceGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { private val pdgSetter = object : IVisitor() { /** - * Collects the data and control dependency edges of a node and adds them to the program - * dependency edges + * Collects the data and control dependence edges of a node and adds them to the program + * dependence edges */ override fun visit(t: Node) { t.addAllPrevPDG(t.prevDFG) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt index 954f27c74a..9be5348a9d 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt @@ -103,7 +103,7 @@ class ProgramDependenceGraphPassTest { .registerLanguage(TestLanguage("::")) .defaultPasses() .registerPass() - .registerPass() + .registerPass() .build() ) .build { @@ -132,7 +132,7 @@ class ProgramDependenceGraphPassTest { .registerLanguage(TestLanguage("::")) .defaultPasses() .registerPass() - .registerPass() + .registerPass() .build() ) .build { From 35a7d7397622b377cdf611ab491d9b0327e5ea1f Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Mon, 3 Jul 2023 19:15:33 +0200 Subject: [PATCH 46/53] rename pdgSetter to visitor --- .../fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt index 6978e60681..c9c2523ed3 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt @@ -40,7 +40,7 @@ import de.fraunhofer.aisec.cpg.processing.strategy.Strategy @DependsOn(DFGPass::class) @DependsOn(ControlFlowSensitiveDFGPass::class, softDependency = true) class ProgramDependenceGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) { - private val pdgSetter = + private val visitor = object : IVisitor() { /** * Collects the data and control dependence edges of a node and adds them to the program @@ -65,6 +65,6 @@ class ProgramDependenceGraphPass(ctx: TranslationContext) : TranslationUnitPass( } private fun handle(node: Node) { - node.accept(Strategy::AST_FORWARD, pdgSetter) + node.accept(Strategy::AST_FORWARD, visitor) } } From 3466320069a4dd046ed6357171b1986697715cc8 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 7 Jul 2023 16:06:45 +0200 Subject: [PATCH 47/53] add new edge property DEPENDENCE --- .../de/fraunhofer/aisec/cpg/graph/edge/Properties.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/Properties.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/Properties.kt index 5fbb3510b9..80b1c2e74a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/Properties.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/Properties.kt @@ -38,6 +38,9 @@ package de.fraunhofer.aisec.cpg.graph.edge * * [UNREACHABLE]:(boolean) True if the edge flows into unreachable code i.e. a branch condition * which is always false. + * + * [DEPENDENCE]: ([DependenceType] Specifies the type of dependence the property edge might + * represent */ enum class Properties { INDEX, @@ -45,5 +48,12 @@ enum class Properties { NAME, INSTANTIATION, UNREACHABLE, - ACCESS + ACCESS, + DEPENDENCE +} + +/** The types of dependencies that might be represented in the CPG */ +enum class DependenceType { + CONTROL, + DATA } From 104fa005b8c9257fe2cf30b829dc48c8489beebc Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 7 Jul 2023 16:17:52 +0200 Subject: [PATCH 48/53] use new DEPENDENCE property for PDGEdges and change addAllPrevPDGEdges function to add the PropertyEdge to both nodes which makes addAllNextPDGEdges unnecessary --- .../de/fraunhofer/aisec/cpg/graph/Node.kt | 23 +++++++++---------- .../cpg/passes/ProgramDependenceGraphPass.kt | 7 +++--- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 2b44118e42..957b4998ea 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 @@ -36,6 +36,7 @@ import de.fraunhofer.aisec.cpg.graph.declarations.MethodDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.RecordDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration import de.fraunhofer.aisec.cpg.graph.declarations.TypedefDeclaration +import de.fraunhofer.aisec.cpg.graph.edge.DependenceType import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge.Companion.unwrap @@ -209,7 +210,7 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider protected set /** Virtual property for accessing the parents of the Program Dependence Graph (PDG). */ - var prevPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) + var prevPDG by PropertyEdgeDelegate(Node::prevPDGEdges, false) var typedefs: MutableSet = HashSet() @@ -287,20 +288,18 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider prev.forEach { it.nextDFG.add(this) } } - fun addAllPrevPDG(prev: Collection) { - addAllPrevPDGEdges(prev.map { PropertyEdge(this, it) }) + fun addAllPrevPDG(prev: Collection, dependenceType: DependenceType) { + addAllPrevPDGEdges(prev.map { PropertyEdge(it, this) }, dependenceType) } - fun addAllPrevPDGEdges(prev: Collection>) { - prevPDGEdges.addAll(prev.map { PropertyEdge(it) }) - } - - fun addAllNextPDG(next: Collection) { - addAllNextPDGEdges(next.map { PropertyEdge(this, it) }) - } + fun addAllPrevPDGEdges(prev: Collection>, dependenceType: DependenceType) { - fun addAllNextPDGEdges(next: Collection>) { - nextPDGEdges.addAll(next.map { PropertyEdge(it) }) + prev.forEach { + val edge = PropertyEdge(it).apply { addProperty(Properties.DEPENDENCE, dependenceType) } + this.prevPDGEdges.add(edge) + val other = if (it.start != this) it.start else it.end + other.nextPDGEdges.add(edge) + } } fun removePrevDFG(prev: Node?) { diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt index c9c2523ed3..ff9ca57c51 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt @@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.passes import de.fraunhofer.aisec.cpg.TranslationContext import de.fraunhofer.aisec.cpg.graph.* import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration +import de.fraunhofer.aisec.cpg.graph.edge.DependenceType import de.fraunhofer.aisec.cpg.passes.order.DependsOn import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy @@ -47,10 +48,8 @@ class ProgramDependenceGraphPass(ctx: TranslationContext) : TranslationUnitPass( * dependence edges */ override fun visit(t: Node) { - t.addAllPrevPDG(t.prevDFG) - t.addAllPrevPDGEdges(t.prevCDGEdges) - t.addAllNextPDG(t.nextDFG) - t.addAllNextPDGEdges(t.nextCDGEdges) + t.addAllPrevPDG(t.prevDFG, DependenceType.DATA) + t.addAllPrevPDGEdges(t.prevCDGEdges, DependenceType.CONTROL) } } From ae1c1734803a039855db530ee935d7d2189b902b Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Fri, 7 Jul 2023 16:20:05 +0200 Subject: [PATCH 49/53] change test to not rely on hashCode for comparing the lists --- .../passes/ProgramDependenceGraphPassTest.kt | 59 +++++++++++++------ 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt index 9be5348a9d..5f703be380 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt @@ -34,14 +34,17 @@ import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.builder.* +import de.fraunhofer.aisec.cpg.graph.edge.DependenceType +import de.fraunhofer.aisec.cpg.graph.edge.Properties import de.fraunhofer.aisec.cpg.graph.edge.PropertyEdge import de.fraunhofer.aisec.cpg.graph.functions import de.fraunhofer.aisec.cpg.graph.get import de.fraunhofer.aisec.cpg.processing.IVisitor import de.fraunhofer.aisec.cpg.processing.strategy.Strategy +import java.util.* import java.util.stream.Stream -import kotlin.test.assertContentEquals import kotlin.test.assertNotNull +import kotlin.test.assertTrue import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource @@ -60,29 +63,51 @@ class ProgramDependenceGraphPassTest { object : IVisitor() { override fun visit(t: Node) { val expectedPrevEdges = - (t.prevCDGEdges + t.prevDFG.map { PropertyEdge(t, it) }).sortedBy { - it.hashCode() - } - assertContentEquals( - expectedPrevEdges, - t.prevPDGEdges.sortedBy { it.hashCode() }, - "prevPDGEdges did not contain all prevCDGEdges and edges to all prevDFG" - ) + t.prevCDGEdges.map { + it.apply { addProperty(Properties.DEPENDENCE, DependenceType.CONTROL) } + } + + t.prevDFG.map { + PropertyEdge(it, t).apply { + addProperty(Properties.DEPENDENCE, DependenceType.DATA) + } + } + assertTrue( + "prevPDGEdges did not contain all prevCDGEdges and edges to all prevDFG.\n" + + "expectedPrevEdges: ${expectedPrevEdges.sortedBy { it.hashCode() }}\n" + + "actualPrevEdges: ${t.prevPDGEdges.sortedBy { it.hashCode() }}" + ) { + compareListWithoutOrder(expectedPrevEdges, t.prevPDGEdges) + } val expectedNextEdges = - (t.nextCDGEdges + t.nextDFG.map { PropertyEdge(t, it) }).sortedBy { - it.hashCode() - } - assertContentEquals( - expectedNextEdges, - t.nextPDGEdges.sortedBy { it.hashCode() }, - "prevPDGEdges did not contain all nextCDGEdges and edges to all nextDFG" - ) + t.nextCDGEdges.map { + it.apply { addProperty(Properties.DEPENDENCE, DependenceType.CONTROL) } + } + + t.nextDFG.map { + PropertyEdge(t, it).apply { + addProperty(Properties.DEPENDENCE, DependenceType.DATA) + } + } + assertTrue( + "nextPDGEdges did not contain all nextCDGEdges and edges to all nextDFG." + + "\nexpectedNextEdges: ${expectedNextEdges.sortedBy { it.hashCode() }}" + + "\nactualNextEdges: ${t.nextPDGEdges.sortedBy { it.hashCode() }}" + ) { + compareListWithoutOrder(expectedNextEdges, t.nextPDGEdges) + } } } ) } + private fun compareListWithoutOrder(expected: List, actual: List): Boolean { + val expectedWithDuplicatesGrouped = expected.groupingBy { it }.eachCount() + val actualWithDuplicatesGrouped = actual.groupingBy { it }.eachCount() + + return expected.size == actual.size && + expectedWithDuplicatesGrouped == actualWithDuplicatesGrouped + } + companion object { fun testFrontend(config: TranslationConfiguration): TestLanguageFrontend { val ctx = TranslationContext(config, ScopeManager(), TypeManager()) From 01d49229876873f9d6b067476410760ac32562c8 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Fri, 28 Jul 2023 15:28:08 +0200 Subject: [PATCH 50/53] Fix problems of merge --- .../aisec/cpg/passes/UnreachableEOGPass.kt | 1 - .../kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt | 6 +----- .../fraunhofer/aisec/cpg/graph/builder/Fluent.kt | 15 +-------------- .../cpg/passes/ProgramDependenceGraphPassTest.kt | 6 +----- 4 files changed, 3 insertions(+), 25 deletions(-) diff --git a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt index 03a2164e64..c867b09ed6 100644 --- a/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt +++ b/cpg-analysis/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/UnreachableEOGPass.kt @@ -191,7 +191,6 @@ private fun handleWhileStatement( // For all other edges, we simply propagate the reachability property of the edge which // made us come here. remainingEdges.forEach { state.push(it, state[enteringEdge]) } - } /** 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 1a6ea8fbff..12e482f0fb 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 @@ -47,11 +47,7 @@ import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.helpers.SubgraphWalker import de.fraunhofer.aisec.cpg.helpers.neo4j.LocationConverter import de.fraunhofer.aisec.cpg.helpers.neo4j.NameConverter -import de.fraunhofer.aisec.cpg.passes.ControlDependenceGraphPass -import de.fraunhofer.aisec.cpg.passes.ControlFlowSensitiveDFGPass -import de.fraunhofer.aisec.cpg.passes.DFGPass -import de.fraunhofer.aisec.cpg.passes.EvaluationOrderGraphPass -import de.fraunhofer.aisec.cpg.passes.FilenameMapper +import de.fraunhofer.aisec.cpg.passes.* import de.fraunhofer.aisec.cpg.processing.IVisitable import de.fraunhofer.aisec.cpg.sarif.PhysicalLocation import java.util.* diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt index 159de7d3ea..24af1fcb35 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/builder/Fluent.kt @@ -469,6 +469,7 @@ fun LanguageFrontend<*, *>.ifStmt(init: IfStatement.() -> Unit): IfStatement { * used to create further sub-nodes as well as configuring the created node itself. */ context(StatementHolder) + fun LanguageFrontend<*, *>.forEachStmt(init: ForEachStatement.() -> Unit): ForEachStatement { val node = newForEachStatement() @@ -610,20 +611,6 @@ fun LanguageFrontend<*, *>.loopBody(init: CompoundStatement.() -> Unit): Compoun return node } -/** - * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the - * [WhileStatement.statement] of the nearest enclosing [WhileStatement]. The [init] block can be - * used to create further sub-nodes as well as configuring the created node itself. - */ -context(ForEachStatement) - -fun LanguageFrontend.loopBody(init: CompoundStatement.() -> Unit): CompoundStatement { - val node = newCompoundStatement() - init(node) - statement = node - - return node -} /** * Creates a new [CompoundStatement] in the Fluent Node DSL and sets it to the diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt index 5f703be380..3780e6a55a 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt @@ -25,14 +25,10 @@ */ package de.fraunhofer.aisec.cpg.passes -import de.fraunhofer.aisec.cpg.ScopeManager -import de.fraunhofer.aisec.cpg.TranslationConfiguration -import de.fraunhofer.aisec.cpg.TranslationContext -import de.fraunhofer.aisec.cpg.TranslationResult +import de.fraunhofer.aisec.cpg.* import de.fraunhofer.aisec.cpg.frontends.TestLanguage import de.fraunhofer.aisec.cpg.frontends.TestLanguageFrontend import de.fraunhofer.aisec.cpg.graph.Node -import de.fraunhofer.aisec.cpg.graph.TypeManager import de.fraunhofer.aisec.cpg.graph.builder.* import de.fraunhofer.aisec.cpg.graph.edge.DependenceType import de.fraunhofer.aisec.cpg.graph.edge.Properties From 93aec2be0bc85c4b22f5072fe8bcd55994cb2a86 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 2 Aug 2023 15:28:46 +0200 Subject: [PATCH 51/53] change to use DFG PropertyEdges instead of the nodes --- .../fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt index ff9ca57c51..c31d82aeb1 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPass.kt @@ -48,7 +48,7 @@ class ProgramDependenceGraphPass(ctx: TranslationContext) : TranslationUnitPass( * dependence edges */ override fun visit(t: Node) { - t.addAllPrevPDG(t.prevDFG, DependenceType.DATA) + t.addAllPrevPDGEdges(t.prevDFGEdges, DependenceType.DATA) t.addAllPrevPDGEdges(t.prevCDGEdges, DependenceType.CONTROL) } } From ed0255dc6fd38ad4c0ccfeaa42d9ac36f904a354 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 2 Aug 2023 16:42:16 +0200 Subject: [PATCH 52/53] change to use sets for storing PDG and adjust PropertyEdgeSetDelegate to use Collection instead of List --- .../src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt | 8 ++++---- .../de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt index 12e482f0fb..7de7a6996f 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 @@ -209,20 +209,20 @@ open class Node : IVisitable, Persistable, LanguageProvider, ScopeProvider /** Outgoing Program Dependence Edges. */ @PopulatedByPass(ProgramDependenceGraphPass::class) @Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING) - var nextPDGEdges: MutableList> = mutableListOf() + var nextPDGEdges: MutableSet> = mutableSetOf() protected set /** Virtual property for accessing the children of the Program Dependence Graph (PDG). */ - var nextPDG by PropertyEdgeDelegate(Node::nextPDGEdges, true) + var nextPDG: MutableSet by PropertyEdgeSetDelegate(Node::nextPDGEdges, true) /** Incoming Program Dependence Edges. */ @PopulatedByPass(ProgramDependenceGraphPass::class) @Relationship(value = "PDG", direction = Relationship.Direction.INCOMING) - var prevPDGEdges: MutableList> = mutableListOf() + var prevPDGEdges: MutableSet> = mutableSetOf() protected set /** Virtual property for accessing the parents of the Program Dependence Graph (PDG). */ - var prevPDG by PropertyEdgeDelegate(Node::prevPDGEdges, false) + var prevPDG: MutableSet by PropertyEdgeSetDelegate(Node::prevPDGEdges, false) var typedefs: MutableSet = HashSet() diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt index 8e74020131..654d450377 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edge/PropertyEdge.kt @@ -377,11 +377,11 @@ class PropertyEdgeDelegate( /** Similar to a [PropertyEdgeDelegate], but with a [Set] instead of [List]. */ @Transient class PropertyEdgeSetDelegate( - val edge: KProperty1>>, + val edge: KProperty1>>, val outgoing: Boolean = true ) { operator fun getValue(thisRef: S, property: KProperty<*>): MutableSet { - return PropertyEdge.unwrap(edge.get(thisRef), outgoing).toMutableSet() + return PropertyEdge.unwrap(edge.get(thisRef).toList(), outgoing).toMutableSet() } operator fun setValue(thisRef: S, property: KProperty<*>, value: MutableSet) { From 4aa9c7ffb8256ecd4754d7efb837b6917c8720c9 Mon Sep 17 00:00:00 2001 From: Selina Lin Date: Wed, 2 Aug 2023 17:00:35 +0200 Subject: [PATCH 53/53] fix types in test --- .../aisec/cpg/passes/ProgramDependenceGraphPassTest.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt index 3780e6a55a..36581aab5c 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/passes/ProgramDependenceGraphPassTest.kt @@ -72,7 +72,7 @@ class ProgramDependenceGraphPassTest { "expectedPrevEdges: ${expectedPrevEdges.sortedBy { it.hashCode() }}\n" + "actualPrevEdges: ${t.prevPDGEdges.sortedBy { it.hashCode() }}" ) { - compareListWithoutOrder(expectedPrevEdges, t.prevPDGEdges) + compareCollectionWithoutOrder(expectedPrevEdges, t.prevPDGEdges) } val expectedNextEdges = @@ -89,14 +89,17 @@ class ProgramDependenceGraphPassTest { "\nexpectedNextEdges: ${expectedNextEdges.sortedBy { it.hashCode() }}" + "\nactualNextEdges: ${t.nextPDGEdges.sortedBy { it.hashCode() }}" ) { - compareListWithoutOrder(expectedNextEdges, t.nextPDGEdges) + compareCollectionWithoutOrder(expectedNextEdges, t.nextPDGEdges) } } } ) } - private fun compareListWithoutOrder(expected: List, actual: List): Boolean { + private fun compareCollectionWithoutOrder( + expected: Collection, + actual: Collection + ): Boolean { val expectedWithDuplicatesGrouped = expected.groupingBy { it }.eachCount() val actualWithDuplicatesGrouped = actual.groupingBy { it }.eachCount()