Skip to content

Commit

Permalink
Add cpg-concepts module (#1932)
Browse files Browse the repository at this point in the history
This PR adds the cpg-concepts module and introduces Concept and Operation nodes.
The new module is enabled for cpg-{neo4j,console}.
There is an extension function to access those nodes as well as an integration test using the neo4j module.

---------

Co-authored-by: Christian Banse <[email protected]>
  • Loading branch information
maximiliankaul and oxisto authored Jan 15, 2025
1 parent 80d8100 commit 64dd8fc
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 18 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ build.gradle.kts @oxisto
.github @oxisto

cpg-language-ini @maximiliankaul
cpg-concepts @maximiliankaul
52 changes: 52 additions & 0 deletions cpg-concepts/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2021, 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.
*
* $$$$$$\ $$$$$$$\ $$$$$$\
* $$ __$$\ $$ __$$\ $$ __$$\
* $$ / \__|$$ | $$ |$$ / \__|
* $$ | $$$$$$$ |$$ |$$$$\
* $$ | $$ ____/ $$ |\_$$ |
* $$ | $$\ $$ | $$ | $$ |
* \$$$$$ |$$ | \$$$$$ |
* \______/ \__| \______/
*
*/
plugins {
id("cpg.frontend-conventions")
}

publishing {
publications {
named<MavenPublication>("cpg-concepts") {
pom {
artifactId = "cpg-concepts"
name.set("Code Property Graph - Concepts")
description.set("A 'concepts' extension for the CPG")
}
}
}
}

dependencies {
// to evaluate some test cases
testImplementation(project(":cpg-analysis"))

// We depend on the Python frontend for the integration tests, but the frontend is only available if enabled.
// If it's not available, the integration tests fail (which is ok). But if we would directly reference the
// project here, the build system would fail any task since it will not find a non-enabled project.
findProject(":cpg-language-python")?.also {
integrationTestImplementation(it)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2024, Fraunhofer AISEC. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $$$$$$\ $$$$$$$\ $$$$$$\
* $$ __$$\ $$ __$$\ $$ __$$\
* $$ / \__|$$ | $$ |$$ / \__|
* $$ | $$$$$$$ |$$ |$$$$\
* $$ | $$ ____/ $$ |\_$$ |
* $$ | $$\ $$ | $$ | $$ |
* \$$$$$ |$$ | \$$$$$ |
* \______/ \__| \______/
*
*/
package de.fraunhofer.aisec.cpg.graph

import de.fraunhofer.aisec.cpg.graph.concepts.Concept
import de.fraunhofer.aisec.cpg.graph.concepts.Operation

/**
* Retrieves a set of all [Concept] nodes associated with this [Node] and its AST children
* ([Node.nodes]).
*
* @return A set containing all [Concept] nodes found in the overlays of the [Node] and its
* children.
*/
val Node.conceptNodes: Set<Concept<*>>
get() = this.nodes.flatMapTo(mutableSetOf()) { it.overlays.filterIsInstance<Concept<*>>() }

/**
* Retrieves a set of all [Operation] nodes associated with this [Node] and its AST children
* ([Node.nodes]).
*
* @return A set containing all [Operation] nodes found in the overlays of the [Node] and its
* children.
*/
val Node.operationNodes: Set<Operation>
get() = this.nodes.flatMapTo(mutableSetOf()) { it.overlays.filterIsInstance<Operation>() }
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,19 @@
*/
package de.fraunhofer.aisec.cpg.graph.concepts

import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.OverlayNode

/**
* Represents a new concept added to the CPG. This is intended for modelling "concepts" like
* logging, files, databases. The relevant operations on this concept are modeled as [Operation]s
* and stored in [ops].
*/
abstract class Concept<T : Operation>() : OverlayNode() {
abstract class Concept<T : Operation>(underlyingNode: Node) : OverlayNode() {
init {
this.underlyingNode = underlyingNode
}

/** All [Operation]s belonging to this concept. */
val ops: MutableSet<T> = mutableSetOf()
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,19 @@
*/
package de.fraunhofer.aisec.cpg.graph.concepts

import de.fraunhofer.aisec.cpg.graph.Node
import de.fraunhofer.aisec.cpg.graph.OverlayNode

/**
* Represents an operation executed on/with a [Concept] (stored in [concept]). This is typically a
* `write` on a file or log object or an `execute` on a database.
*/
abstract class Operation(
underlyingNode: Node,
/** The [Concept] this operation belongs to. */
val concept: Concept<*>
) : OverlayNode()
open val concept: Concept<*>,
) : OverlayNode() {
init {
this.underlyingNode = underlyingNode
}
}
14 changes: 14 additions & 0 deletions cpg-concepts/src/test/resources/log4j2.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Configuration status="WARN">
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss,SSS} %-5p %C{1} %m%n"/>
<ThresholdFilter level="DEBUG"/>
</Console>
</Appenders>
<Loggers>
<Logger level="DEBUG" name="de.fraunhofer.aisec.cpg"/>
<Root level="DEBUG">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>
1 change: 1 addition & 0 deletions cpg-console/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,5 @@ dependencies {

testImplementation(testFixtures(projects.cpgCore))

implementation(project(":cpg-concepts"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@ abstract class OverlayNode() : Node() {
/** All [OverlayNode]s nodes are connected to an original cpg [Node] by this. */
val underlyingNodeEdge: OverlaySingleEdge =
OverlaySingleEdge(this, of = null, mirrorProperty = Node::overlayEdges, outgoing = false)

var underlyingNode by unwrapping(OverlayNode::underlyingNodeEdge)
}
2 changes: 2 additions & 0 deletions cpg-neo4j/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,6 @@ dependencies {
findProject(":cpg-language-cxx")?.also {
integrationTestImplementation(it)
}
integrationTestImplementation(project(":cpg-concepts"))
implementation(project(":cpg-concepts"))
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,29 +71,38 @@ class Neo4JTest {
val connectCall = result.calls["connect"]
assertNotNull(connectCall)

abstract class NetworkingOperation(concept: Concept<out Operation>) : Operation(concept)
class Connect(concept: Concept<out Operation>) : NetworkingOperation(concept)
class Networking() : Concept<NetworkingOperation>()

abstract class FileOperation(concept: Concept<out Operation>) : Operation(concept)
class FileHandling() : Concept<FileOperation>()

val nw = Networking()
abstract class NetworkingOperation(underlyingNode: Node, concept: Concept<out Operation>) :
Operation(underlyingNode = underlyingNode, concept = concept)
class Connect(underlyingNode: Node, concept: Concept<out Operation>) :
NetworkingOperation(underlyingNode = underlyingNode, concept = concept)
class Networking(underlyingNode: Node) :
Concept<NetworkingOperation>(underlyingNode = underlyingNode)

abstract class FileOperation(underlyingNode: Node, concept: Concept<out Operation>) :
Operation(underlyingNode = underlyingNode, concept = concept)
class FileHandling(underlyingNode: Node) :
Concept<FileOperation>(underlyingNode = underlyingNode)

val nw = Networking(underlyingNode = tu)
nw.name = Name("Networking")
nw.underlyingNode = tu

val connect = Connect(concept = nw)
connect.underlyingNode = connectCall
val connect = Connect(underlyingNode = connectCall, concept = nw)
connect.name = Name("connect")
nw.ops += connect

val f = FileHandling()
val f = FileHandling(underlyingNode = tu)
f.name = Name("FileHandling")
f.underlyingNode = tu

assertEquals(setOf<Node>(connect), connectCall.overlays)
assertEquals(setOf<Node>(nw, f), tu.overlays)

assertEquals(
2,
tu.conceptNodes.size,
"Expected to find the `Networking` and `FileHandling` concept.",
)
assertEquals(1, tu.operationNodes.size, "Expected to find the `Connect` operation.")

application.pushToNeo4j(result)
}
}
2 changes: 1 addition & 1 deletion gradle.properties.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ enableLLVMFrontend=true
enableTypeScriptFrontend=true
enableRubyFrontend=true
enableJVMFrontend=true
enableINIFrontend=true
enableINIFrontend=true
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ include(":cpg-core")
include(":cpg-analysis")
include(":cpg-neo4j")
include(":cpg-console")
include(":cpg-concepts")

// this code block also exists in the root build.gradle.kts
val enableJavaFrontend: Boolean by extra {
Expand Down Expand Up @@ -62,4 +63,4 @@ if (enableINIFrontend) include(":cpg-language-ini")

kover {
enableCoverage()
}
}

0 comments on commit 64dd8fc

Please sign in to comment.