From 64dd8fc0ea8770c05b4e0c274a07e4e0c0d9dcbd Mon Sep 17 00:00:00 2001 From: Maximilian Kaul Date: Wed, 15 Jan 2025 14:57:33 +0100 Subject: [PATCH] Add cpg-concepts module (#1932) 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 --- .github/CODEOWNERS | 1 + cpg-concepts/build.gradle.kts | 52 +++++++++++++++++++ .../fraunhofer/aisec/cpg/graph/Extension.kt | 49 +++++++++++++++++ .../aisec/cpg/graph/concepts/Concept.kt | 7 ++- .../aisec/cpg/graph/concepts/Operation.kt | 10 +++- cpg-concepts/src/test/resources/log4j2.xml | 14 +++++ cpg-console/build.gradle.kts | 1 + .../fraunhofer/aisec/cpg/graph/OverlayNode.kt | 1 + cpg-neo4j/build.gradle.kts | 2 + .../de/fraunhofer/aisec/neo4j/Neo4JTest.kt | 35 ++++++++----- gradle.properties.example | 2 +- settings.gradle.kts | 3 +- 12 files changed, 159 insertions(+), 18 deletions(-) create mode 100644 cpg-concepts/build.gradle.kts create mode 100644 cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extension.kt rename {cpg-core => cpg-concepts}/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Concept.kt (88%) rename {cpg-core => cpg-concepts}/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Operation.kt (88%) create mode 100644 cpg-concepts/src/test/resources/log4j2.xml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5a84f06b679..1299bb80d1d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -30,3 +30,4 @@ build.gradle.kts @oxisto .github @oxisto cpg-language-ini @maximiliankaul +cpg-concepts @maximiliankaul diff --git a/cpg-concepts/build.gradle.kts b/cpg-concepts/build.gradle.kts new file mode 100644 index 00000000000..170be569fd6 --- /dev/null +++ b/cpg-concepts/build.gradle.kts @@ -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("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) + } +} diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extension.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extension.kt new file mode 100644 index 00000000000..c95b027a5a8 --- /dev/null +++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extension.kt @@ -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> + get() = this.nodes.flatMapTo(mutableSetOf()) { it.overlays.filterIsInstance>() } + +/** + * 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 + get() = this.nodes.flatMapTo(mutableSetOf()) { it.overlays.filterIsInstance() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Concept.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Concept.kt similarity index 88% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Concept.kt rename to cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Concept.kt index 8d7aaa42b4f..037a415f2d0 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Concept.kt +++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Concept.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.concepts +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.OverlayNode /** @@ -32,7 +33,11 @@ import de.fraunhofer.aisec.cpg.graph.OverlayNode * logging, files, databases. The relevant operations on this concept are modeled as [Operation]s * and stored in [ops]. */ -abstract class Concept() : OverlayNode() { +abstract class Concept(underlyingNode: Node) : OverlayNode() { + init { + this.underlyingNode = underlyingNode + } + /** All [Operation]s belonging to this concept. */ val ops: MutableSet = mutableSetOf() } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Operation.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Operation.kt similarity index 88% rename from cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Operation.kt rename to cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Operation.kt index 014759d6472..9d5ec481ed2 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Operation.kt +++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Operation.kt @@ -25,6 +25,7 @@ */ package de.fraunhofer.aisec.cpg.graph.concepts +import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.graph.OverlayNode /** @@ -32,6 +33,11 @@ import de.fraunhofer.aisec.cpg.graph.OverlayNode * `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 + } +} diff --git a/cpg-concepts/src/test/resources/log4j2.xml b/cpg-concepts/src/test/resources/log4j2.xml new file mode 100644 index 00000000000..ac6e67063f1 --- /dev/null +++ b/cpg-concepts/src/test/resources/log4j2.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cpg-console/build.gradle.kts b/cpg-console/build.gradle.kts index 2e8bdd213da..d5d504e2edc 100644 --- a/cpg-console/build.gradle.kts +++ b/cpg-console/build.gradle.kts @@ -68,4 +68,5 @@ dependencies { testImplementation(testFixtures(projects.cpgCore)) + implementation(project(":cpg-concepts")) } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt index 48c4f0860f6..28d0ac998ed 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt @@ -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) } diff --git a/cpg-neo4j/build.gradle.kts b/cpg-neo4j/build.gradle.kts index 9a540683968..8380c08ea8d 100644 --- a/cpg-neo4j/build.gradle.kts +++ b/cpg-neo4j/build.gradle.kts @@ -81,4 +81,6 @@ dependencies { findProject(":cpg-language-cxx")?.also { integrationTestImplementation(it) } + integrationTestImplementation(project(":cpg-concepts")) + implementation(project(":cpg-concepts")) } diff --git a/cpg-neo4j/src/integrationTest/kotlin/de/fraunhofer/aisec/neo4j/Neo4JTest.kt b/cpg-neo4j/src/integrationTest/kotlin/de/fraunhofer/aisec/neo4j/Neo4JTest.kt index 6d1100384b0..8fb8d8656ee 100644 --- a/cpg-neo4j/src/integrationTest/kotlin/de/fraunhofer/aisec/neo4j/Neo4JTest.kt +++ b/cpg-neo4j/src/integrationTest/kotlin/de/fraunhofer/aisec/neo4j/Neo4JTest.kt @@ -71,29 +71,38 @@ class Neo4JTest { val connectCall = result.calls["connect"] assertNotNull(connectCall) - abstract class NetworkingOperation(concept: Concept) : Operation(concept) - class Connect(concept: Concept) : NetworkingOperation(concept) - class Networking() : Concept() - - abstract class FileOperation(concept: Concept) : Operation(concept) - class FileHandling() : Concept() - - val nw = Networking() + abstract class NetworkingOperation(underlyingNode: Node, concept: Concept) : + Operation(underlyingNode = underlyingNode, concept = concept) + class Connect(underlyingNode: Node, concept: Concept) : + NetworkingOperation(underlyingNode = underlyingNode, concept = concept) + class Networking(underlyingNode: Node) : + Concept(underlyingNode = underlyingNode) + + abstract class FileOperation(underlyingNode: Node, concept: Concept) : + Operation(underlyingNode = underlyingNode, concept = concept) + class FileHandling(underlyingNode: Node) : + Concept(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(connect), connectCall.overlays) assertEquals(setOf(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) } } diff --git a/gradle.properties.example b/gradle.properties.example index 34749a17cea..55f76e4b8c2 100644 --- a/gradle.properties.example +++ b/gradle.properties.example @@ -9,4 +9,4 @@ enableLLVMFrontend=true enableTypeScriptFrontend=true enableRubyFrontend=true enableJVMFrontend=true -enableINIFrontend=true +enableINIFrontend=true \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index dea0da5cfef..91edf9c911e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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 { @@ -62,4 +63,4 @@ if (enableINIFrontend) include(":cpg-language-ini") kover { enableCoverage() -} \ No newline at end of file +}