Skip to content

Commit

Permalink
Add Program Dependency Graph (#1227)
Browse files Browse the repository at this point in the history
* Try to use a more formal worklist EOG worklist iteration for the CFSensitiveDFG

* Add new SplitsControlFlow interface

* New unreachable EOG edges pass

* Handle AssignExpressions in ControlFlowSensitiveDFGPass

* Mark statements which should be irrelevant because the same edges are set in the normal DFGPass

* Remove setting unnecessary (already existing) edges from CFSensitiveDFGPass

* ConditionalExpression splits control flow

* Fix issue 1141

* Fix issue 1141

* Consistency

* Rename class and add fields

* Generate CDG

* Fix problem with very strange EOG self reference of literals

* Test if statements and cdg

* Add test for foreach loop

* Reduce sonar warnings

* Fix failing tests

* Use HasShortcircuitOperators to generate ShortcircuitOperator objects in Expression Builder

* Little fix

* Integrate review feedback

* More review feedback

* Update cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/EOGWorklist.kt

Co-authored-by: Christian Banse <christian.banse@aisec.fraunhofer.de>

* Merge two iterateEOG functions

* Fluent DSL for loops

* Fix redundant stuff

* Revert "Fluent DSL for loops"

This reverts commit 4ecc621.

* Get rid of "expectedUpdate"

* Change method signatures

* Small cleanup

* Rename lattice to latticeelement

* Remove nullable properties

* Nicer code

* Documentation

* first draft for generating pdg property edges

* change delegate constructor parameter to KProperty1 for consistency

* change PDGEdges properties from using delegates to custom getters

* add EventListener to set pdg edges before neo4j serialization

* change to adding the PDG edges through a new Pass

* fix typos

* add test for the ProgramDependenceGraphPass

* add more tests

* add documentation

* fix format violation

* remove unused delegate class

* rename pass to ProgramDependenceGraphPass and add documentation

* rename pdgSetter to visitor

* add new edge property DEPENDENCE

* use new DEPENDENCE property for PDGEdges and change addAllPrevPDGEdges function to add the PropertyEdge to both nodes which makes addAllNextPDGEdges unnecessary

* change test to not rely on hashCode for comparing the lists

* Fix problems of merge

* change to use DFG PropertyEdges instead of the nodes

* change to use sets for storing PDG and adjust PropertyEdgeSetDelegate to use Collection instead of List

* fix types in test

---------

Co-authored-by: Alexander Kuechler <alexander.kuechler@aisec.fraunhofer.de>
Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com>
Co-authored-by: Christian Banse <christian.banse@aisec.fraunhofer.de>
Co-authored-by: Christian Banse <oxisto@aybaze.com>
  • Loading branch information
5 people committed Aug 8, 2023
1 parent 38d963f commit 7d720c8
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 8 deletions.
40 changes: 35 additions & 5 deletions cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Node.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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
Expand All @@ -46,14 +47,11 @@ 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.*
import kotlin.collections.ArrayList
import org.apache.commons.lang3.builder.ToStringBuilder
import org.apache.commons.lang3.builder.ToStringStyle
import org.neo4j.ogm.annotation.*
Expand Down Expand Up @@ -208,6 +206,24 @@ open class Node : IVisitable<Node>, Persistable, LanguageProvider, ScopeProvider
@PopulatedByPass(DFGPass::class, ControlFlowSensitiveDFGPass::class)
var nextDFG: MutableSet<Node> by PropertyEdgeSetDelegate(Node::nextDFGEdges, true)

/** Outgoing Program Dependence Edges. */
@PopulatedByPass(ProgramDependenceGraphPass::class)
@Relationship(value = "PDG", direction = Relationship.Direction.OUTGOING)
var nextPDGEdges: MutableSet<PropertyEdge<Node>> = mutableSetOf()
protected set

/** Virtual property for accessing the children of the Program Dependence Graph (PDG). */
var nextPDG: MutableSet<Node> by PropertyEdgeSetDelegate(Node::nextPDGEdges, true)

/** Incoming Program Dependence Edges. */
@PopulatedByPass(ProgramDependenceGraphPass::class)
@Relationship(value = "PDG", direction = Relationship.Direction.INCOMING)
var prevPDGEdges: MutableSet<PropertyEdge<Node>> = mutableSetOf()
protected set

/** Virtual property for accessing the parents of the Program Dependence Graph (PDG). */
var prevPDG: MutableSet<Node> by PropertyEdgeSetDelegate(Node::prevPDGEdges, false)

var typedefs: MutableSet<TypedefDeclaration> = HashSet()

/**
Expand Down Expand Up @@ -300,6 +316,20 @@ open class Node : IVisitable<Node>, Persistable, LanguageProvider, ScopeProvider
prev.forEach { addPrevDFG(it, properties.toMutableMap()) }
}

fun addAllPrevPDG(prev: Collection<Node>, dependenceType: DependenceType) {
addAllPrevPDGEdges(prev.map { PropertyEdge(it, this) }, dependenceType)
}

fun addAllPrevPDGEdges(prev: Collection<PropertyEdge<Node>>, dependenceType: DependenceType) {

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?) {
if (prev != null) {
val thisRemove =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,22 @@ 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,
BRANCH,
NAME,
INSTANTIATION,
UNREACHABLE,
ACCESS
ACCESS,
DEPENDENCE
}

/** The types of dependencies that might be represented in the CPG */
enum class DependenceType {
CONTROL,
DATA
}
Original file line number Diff line number Diff line change
Expand Up @@ -377,11 +377,11 @@ class PropertyEdgeDelegate<T : Node, S : Node>(
/** Similar to a [PropertyEdgeDelegate], but with a [Set] instead of [List]. */
@Transient
class PropertyEdgeSetDelegate<T : Node, S : Node>(
val edge: KProperty1<S, List<PropertyEdge<T>>>,
val edge: KProperty1<S, Collection<PropertyEdge<T>>>,
val outgoing: Boolean = true
) {
operator fun getValue(thisRef: S, property: KProperty<*>): MutableSet<T> {
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<T>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.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

/**
* 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 ProgramDependenceGraphPass(ctx: TranslationContext) : TranslationUnitPass(ctx) {
private val visitor =
object : IVisitor<Node>() {
/**
* Collects the data and control dependence edges of a node and adds them to the program
* dependence edges
*/
override fun visit(t: Node) {
t.addAllPrevPDGEdges(t.prevDFGEdges, DependenceType.DATA)
t.addAllPrevPDGEdges(t.prevCDGEdges, DependenceType.CONTROL)
}
}

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, visitor)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* 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.*
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.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.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

class ProgramDependenceGraphPassTest {

@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)

main.accept(
Strategy::AST_FORWARD,
object : IVisitor<Node>() {
override fun visit(t: Node) {
val expectedPrevEdges =
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() }}"
) {
compareCollectionWithoutOrder(expectedPrevEdges, t.prevPDGEdges)
}

val expectedNextEdges =
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() }}"
) {
compareCollectionWithoutOrder(expectedNextEdges, t.nextPDGEdges)
}
}
}
)
}

private fun <T> compareCollectionWithoutOrder(
expected: Collection<T>,
actual: Collection<T>
): 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())
val language = config.languages.filterIsInstance<TestLanguage>().first()
return TestLanguageFrontend(language.namespaceDelimiter, language, ctx)
}

@JvmStatic
fun provideTranslationResultForPDGTest() =
Stream.of(
Arguments.of(getIfTest(), "if statement"),
Arguments.of(getWhileLoopTest(), "while loop")
)

private fun getIfTest() =
testFrontend(
TranslationConfiguration.builder()
.registerLanguage(TestLanguage("::"))
.defaultPasses()
.registerPass<ControlDependenceGraphPass>()
.registerPass<ProgramDependenceGraphPass>()
.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") }
}
}
}
}
}

private fun getWhileLoopTest() =
testFrontend(
TranslationConfiguration.builder()
.registerLanguage(TestLanguage("::"))
.defaultPasses()
.registerPass<ControlDependenceGraphPass>()
.registerPass<ProgramDependenceGraphPass>()
.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")) }
}
}
}
}
}
}
}

0 comments on commit 7d720c8

Please sign in to comment.