Repository: Fraunhofer-AISEC/cpg
Pull request #2124: Add concept and operation builders
Author: login: KuechA
Created: 2025-03-11T16:10:05Z | Merged: 2025-03-13T16:31:17Z

* [x] Introduce builders for general `Concept` and `Operation`.
* [x] Introduce builders for specific classes
* [x] Transfer passes to builders
* [x] Use the builders in integrationTests

Fixes #2101 

Linked issue #2101: Provider builders for `Concept` and `Operation` nodes
We should harmonize the creation of concepts and operations:

- code and location?
- name
- ....

We decided that we want individual builders for each concept and operation. This might be initial manual work but in the long run we are thinking about auto-generating the concepts from an ontology file. It will make sense to provide an underlying generic function that each builder can invoke.
Linked issue comments: user: login: immqu
created_at: 2025-03-11T14:34:36Z
body: harmonize EOG creation with _insert node before_ magic
author_association: NONE

Diff:
diff --git a/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/MemoryTest.kt b/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/MemoryTest.kt
index 17991b214a2..256749b0e58 100644
--- a/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/MemoryTest.kt
+++ b/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/MemoryTest.kt
@@ -30,13 +30,15 @@ import de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage
 import de.fraunhofer.aisec.cpg.graph.*
 import de.fraunhofer.aisec.cpg.graph.Forward
 import de.fraunhofer.aisec.cpg.graph.Interprocedural
-import de.fraunhofer.aisec.cpg.graph.concepts.diskEncryption.Cipher
 import de.fraunhofer.aisec.cpg.graph.concepts.diskEncryption.Encrypt
 import de.fraunhofer.aisec.cpg.graph.concepts.diskEncryption.GetSecret
-import de.fraunhofer.aisec.cpg.graph.concepts.diskEncryption.Secret
+import de.fraunhofer.aisec.cpg.graph.concepts.diskEncryption.newCipher
+import de.fraunhofer.aisec.cpg.graph.concepts.diskEncryption.newEncryptOperation
+import de.fraunhofer.aisec.cpg.graph.concepts.diskEncryption.newSecret
 import de.fraunhofer.aisec.cpg.graph.concepts.memory.DeAllocate
-import de.fraunhofer.aisec.cpg.graph.concepts.memory.Memory
 import de.fraunhofer.aisec.cpg.graph.concepts.memory.MemoryManagementMode
+import de.fraunhofer.aisec.cpg.graph.concepts.memory.newDeallocate
+import de.fraunhofer.aisec.cpg.graph.concepts.memory.newMemory
 import de.fraunhofer.aisec.cpg.graph.edges.*
 import de.fraunhofer.aisec.cpg.graph.statements.expressions.DeleteExpression
 import de.fraunhofer.aisec.cpg.query.Must
@@ -55,17 +57,16 @@ class MemoryTest {
                 it.registerLanguage<PythonLanguage>()
             }
         // Secrets (key) concepts
-        val key = Secret(underlyingNode = assertNotNull(result.variables["key"]))
+        val key = result.newSecret(underlyingNode = assertNotNull(result.variables["key"]))
         val getSecret =
             GetSecret(
                 underlyingNode = assertNotNull(result.functions["get_secret_from_server"]),
                 concept = key,
             )
-        key.ops += getSecret
 
         // Cipher (encryption) concepts
         val cipher =
-            Cipher(
+            result.newCipher(
                 underlyingNode =
                     assertNotNull(
                         assertNotNull(result.calls["encrypt"]).argumentEdges["cipher"]?.end
@@ -76,27 +77,23 @@ class MemoryTest {
         cipher.blockSize = cipherAndSize?.get(1)?.toIntOrNull()
         assertEquals("AES", cipher.cipherName)
         assertEquals(256, cipher.blockSize)
-        val encrypt =
-            Encrypt(
-                underlyingNode = assertNotNull(result.functions["encrypt"]),
-                concept = cipher,
-                key = key,
-            )
-        cipher.ops += encrypt
+        result.newEncryptOperation(
+            underlyingNode = assertNotNull(result.calls["encrypt"]),
+            concept = cipher,
+            key = key,
+        )
 
         // Memory concepts
         val memory =
-            Memory(
+            result.newMemory(
                 underlyingNode = assertNotNull(result.components[DEFAULT_APPLICATION_NAME]),
                 mode = MemoryManagementMode.MANAGED_WITH_GARBAGE_COLLECTION,
             )
-        val ops =
-            result.allChildren<DeleteExpression>().flatMap { delete ->
-                delete.operands.map {
-                    DeAllocate(underlyingNode = delete, concept = memory, what = it)
-                }
+        result.allChildren<DeleteExpression>().flatMap { delete ->
+            delete.operands.map {
+                result.newDeallocate(underlyingNode = delete, concept = memory, what = it)
             }
-        memory.ops += ops
+        }
 
         // Key is used in encryption
         var tree =
@@ -111,9 +108,7 @@ class MemoryTest {
             key.underlyingNode?.let {
                 executionPath(
                     startNode = it,
-                    predicate = { node ->
-                        node.overlayEdges.any { edge -> edge.end is DeAllocate }
-                    },
+                    predicate = { node -> node is DeAllocate },
                     direction = Forward(GraphToFollow.EOG),
                     type = Must,
                     scope = Interprocedural(),
diff --git a/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/file/FileConceptTest.kt b/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/file/FileConceptTest.kt
index bfd3cf6040c..a7e3f1cc973 100644
--- a/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/file/FileConceptTest.kt
+++ b/cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/file/FileConceptTest.kt
@@ -29,9 +29,9 @@ import de.fraunhofer.aisec.cpg.frontends.python.PythonLanguage
 import de.fraunhofer.aisec.cpg.graph.*
 import de.fraunhofer.aisec.cpg.graph.concepts.file.*
 import de.fraunhofer.aisec.cpg.passes.concepts.file.python.PythonFileConceptPass
+import de.fraunhofer.aisec.cpg.query.Must
 import de.fraunhofer.aisec.cpg.query.dataFlow
 import de.fraunhofer.aisec.cpg.query.executionPath
-import de.fraunhofer.aisec.cpg.query.executionPathHelper
 import de.fraunhofer.aisec.cpg.test.BaseTest
 import de.fraunhofer.aisec.cpg.test.analyze
 import java.nio.file.Path
@@ -58,11 +58,7 @@ class FileConceptTest : BaseTest() {
             }
         assertNotNull(result)
 
-        val fileNodes =
-            result.conceptNodes.filterIsInstance<IsFile>() +
-                result.operationNodes.filterIsInstance<
-                    IsFile
-                >() // TODO why can't I use `overlays`? It's empty.
+        val fileNodes = result.allChildrenWithOverlays<IsFile>()
         assertTrue(fileNodes.isNotEmpty())
 
         val file = fileNodes.filterIsInstance<File>().singleOrNull()
@@ -103,16 +99,22 @@ class FileConceptTest : BaseTest() {
         val fileOpen = fileNodes.filterIsInstance<OpenFile>().singleOrNull()
         assertNotNull(fileOpen)
         assertTrue(
-            executionPathHelper(startNode = fileOpen, endNode = readFile),
+            executionPath(startNode = fileOpen, predicate = { it == readFile }).value,
             "Expected to find an execution path from open to read.",
         )
 
         val fileRead = fileNodes.filterIsInstance<ReadFile>().singleOrNull()
         assertNotNull(fileRead)
-        val closeFile = fileNodes.filterIsInstance<CloseFile>().singleOrNull()
-        assertNotNull(closeFile)
+        assertEquals(
+            2,
+            fileNodes.filterIsInstance<CloseFile>().size,
+            "We expect 2 x close (one for normally exiting `with` and one for the `catch` exit) operations.",
+        )
+
+        val executionPathReadToCloseMust =
+            executionPath(startNode = fileRead, type = Must, predicate = { it is CloseFile })
         assertTrue(
-            executionPathHelper(startNode = fileRead, endNode = closeFile),
+            executionPathReadToCloseMust.value,
             "Expected to find an execution path from read to close.",
         )
     }
@@ -133,11 +135,7 @@ class FileConceptTest : BaseTest() {
             }
         assertNotNull(result)
 
-        val fileNodes =
-            result.conceptNodes.filterIsInstance<IsFile>() +
-                result.operationNodes.filterIsInstance<
-                    IsFile
-                >() // TODO why can't I use `overlays`? It's empty.
+        val fileNodes = result.allChildrenWithOverlays<IsFile>()
         assertTrue(fileNodes.isNotEmpty())
 
         val file = fileNodes.filterIsInstance<File>().singleOrNull()
@@ -190,11 +188,7 @@ class FileConceptTest : BaseTest() {
             }
         assertNotNull(result)
 
-        val fileNodes =
-            result.conceptNodes.filterIsInstance<IsFile>() +
-                result.operationNodes.filterIsInstance<
-                    IsFile
-                >() // TODO why can't I use `overlays`? It's empty.
+        val fileNodes = result.allChildrenWithOverlays<IsFile>()
         assertTrue(fileNodes.isNotEmpty())
 
         val maskNode = fileNodes.filterIsInstance<SetFileMask>().singleOrNull()
@@ -287,11 +281,7 @@ class FileConceptTest : BaseTest() {
             }
         assertNotNull(result)
 
-        val conceptNodes =
-            result.conceptNodes.filterIsInstance<IsFile>() +
-                result.operationNodes.filterIsInstance<
-                    IsFile
-                >() // TODO why can't I use `overlays`? It's empty.
+        val conceptNodes = result.allChildrenWithOverlays<IsFile>()
         assertTrue(conceptNodes.isNotEmpty())
 
         val file = conceptNodes.filterIsInstance<File>().singleOrNull()
@@ -309,7 +299,7 @@ class FileConceptTest : BaseTest() {
 
         // Let's find our bad example
         assertTrue(
-            executionPathHelper(startNode = write, endNode = chmod),
+            executionPath(startNode = write, predicate = { it == chmod }).value,
             "Expected to find a violating execution path from write to chmod.",
         )
     }
@@ -332,11 +322,7 @@ class FileConceptTest : BaseTest() {
             }
         assertNotNull(result)
 
-        val conceptNodes =
-            result.conceptNodes.filterIsInstance<IsFile>() +
-                result.operationNodes.filterIsInstance<
-                    IsFile
-                >() // TODO why can't I use `overlays`? It's empty.
+        val conceptNodes = result.allChildrenWithOverlays<IsFile>()
         assertTrue(conceptNodes.isNotEmpty())
 
         val files = conceptNodes.filterIsInstance<File>()
@@ -350,6 +336,7 @@ class FileConceptTest : BaseTest() {
         }
     }
 
+    @Ignore("Issue https://github.com/Fraunhofer-AISEC/cpg/issues/2121")
     @Test
     fun testDelete() {
         val topLevel = Path.of("src", "integrationTest", "resources", "python", "file")
@@ -366,11 +353,7 @@ class FileConceptTest : BaseTest() {
             }
         assertNotNull(result)
 
-        val conceptNodes =
-            result.conceptNodes.filterIsInstance<IsFile>() +
-                result.operationNodes.filterIsInstance<
-                    IsFile
-                >() // TODO why can't I use `overlays`? It's empty.
+        val conceptNodes = result.allChildrenWithOverlays<IsFile>()
         assertTrue(conceptNodes.isNotEmpty())
 
         val file = conceptNodes.filterIsInstance<File>().singleOrNull()
@@ -391,12 +374,12 @@ class FileConceptTest : BaseTest() {
         )
 
         assertTrue(
-            executionPathHelper(startNode = fileRead, endNode = fileDelete),
+            executionPath(startNode = fileRead, predicate = { it == fileDelete }).value,
             "Expected to find an execution path from read to remove.",
         )
 
         assertTrue(
-            executionPathHelper(startNode = fileDelete, endNode = fileWrite),
+            executionPath(startNode = fileDelete, predicate = { it == fileWrite }).value,
             "Expected to find an execution path from remove to write.",
         )
     }
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
new file mode 100644
index 00000000000..54126e4999f
--- /dev/null
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).apply {
+        this.codeAndLocationFrom(underlyingNode)
+        this.name = Name("${ConceptClass::class.simpleName}", underlyingNode.name)
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted in the EOG
+ * after the [underlyingNode] but it is not connected by the DFG.
+ */
+inline fun <reified OperationClass : Operation, ConceptClass : Concept> MetadataProvider
+    .newOperation(
+    constructor: (underlyingNode: Node, concept: ConceptClass) -> (OperationClass),
+    underlyingNode: Node,
+    concept: ConceptClass,
+): OperationClass =
+    constructor(underlyingNode, concept).apply {
+        this.codeAndLocationFrom(underlyingNode)
+        this.name =
+            Name(
+                "${OperationClass::class.simpleName}".replaceFirstChar { it.lowercaseChar() },
+                concept.name,
+            )
+        concept.ops += this
+        underlyingNode.insertNodeAfterwardInEOGPath(this)
+        NodeBuilder.log(this)
+    }
diff --git a/cpg-concepts/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
index ecea87b39bc..8277e7c09b1 100644
--- a/cpg-concepts/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
@@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.graph.concepts
 import de.fraunhofer.aisec.cpg.graph.Name
 import de.fraunhofer.aisec.cpg.graph.Node
 import de.fraunhofer.aisec.cpg.graph.OverlayNode
+import java.util.Objects
 
 /**
  * Represents an operation executed on/with a [Concept] (stored in [concept]). This is typically a
@@ -36,14 +37,17 @@ import de.fraunhofer.aisec.cpg.graph.OverlayNode
 abstract class Operation(
     underlyingNode: Node,
     /** The [Concept] this operation belongs to. */
-    concept: Concept,
+    open val concept: Concept,
 ) : OverlayNode() {
 
-    /** The [Concept] this operation belongs to. */
-    open val concept: Concept = concept.also { it.ops += this }
-
     init {
         this.underlyingNode = underlyingNode
         this::class.simpleName?.let { name = Name(it) }
     }
+
+    override fun equals(other: Any?): Boolean {
+        return other is Operation && super.equals(other) && other.concept == this.concept
+    }
+
+    override fun hashCode() = Objects.hash(super.hashCode(), concept)
 }
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/arch/ArchitectureBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/arch/ArchitectureBuilder.kt
new file mode 100644
index 00000000000..e90243413a6
--- /dev/null
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/arch/ArchitectureBuilder.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2025, 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.concepts.arch
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.concepts.newConcept
+
+/**
+ * Creates a new [Agnostic] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [Agnostic] concept.
+ */
+fun MetadataProvider.newAgnosticOS(underlyingNode: Node) =
+    newConcept(::Agnostic, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [Win32] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [Win32] concept.
+ */
+fun MetadataProvider.newWin32OS(underlyingNode: Node) =
+    newConcept(::Win32, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [POSIX] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [POSIX] concept.
+ */
+fun MetadataProvider.newPosixOS(underlyingNode: Node) =
+    newConcept(::POSIX, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [Darwin] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [Darwin] concept.
+ */
+fun MetadataProvider.newDarwinOS(underlyingNode: Node) =
+    newConcept(::Darwin, underlyingNode = underlyingNode)
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/auth/AuthenticationBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/auth/AuthenticationBuilder.kt
new file mode 100644
index 00000000000..0d7dc199c81
--- /dev/null
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/auth/AuthenticationBuilder.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2025, 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.concepts.auth
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.concepts.newConcept
+import de.fraunhofer.aisec.cpg.graph.concepts.newOperation
+
+/**
+ * Creates a new [TokenBasedAuth] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param token The authentication token, which may be an opaque token.
+ * @return The created [TokenBasedAuth] concept.
+ */
+fun MetadataProvider.newTokenBasedAuth(underlyingNode: Node, token: Node) =
+    newConcept({ TokenBasedAuth(it, token = token) }, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [JwtAuth] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param jwt The JWT containing encoded authentication information.
+ * @param payload The payload.
+ * @return The created [JwtAuth] concept.
+ */
+fun MetadataProvider.newJwtAuth(underlyingNode: Node, jwt: Node, payload: Node) =
+    newConcept({ JwtAuth(it, jwt = jwt, payload = payload) }, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [Authenticate] operation belonging to a certain [Authentication] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param concept The [Authentication] concept to which the operation belongs.
+ * @param credential The credential can be a call (e.g., a function call that reads a header) or a
+ *   variable that holds the value, e.g. the token * @return The created [Authenticate] operation.
+ */
+fun MetadataProvider.newAuthenticate(
+    underlyingNode: Node,
+    concept: Authentication,
+    credential: Node,
+) =
+    newOperation(
+        { underlyingNode, concept ->
+            Authenticate(
+                underlyingNode = underlyingNode,
+                concept = concept,
+                credential = credential,
+            )
+        },
+        underlyingNode = underlyingNode,
+        concept = concept,
+    )
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/Configuration.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/Configuration.kt
index cd3a604e6ec..0ab256f9c34 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/Configuration.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/Configuration.kt
@@ -70,10 +70,6 @@ class Configuration(underlyingNode: Node) : Concept(underlyingNode = underlyingN
 class ConfigurationGroup(underlyingNode: Node, var conf: Configuration) :
     Concept(underlyingNode = underlyingNode) {
     var options: MutableList<ConfigurationOption> = mutableListOf()
-
-    init {
-        conf.groups.add(this)
-    }
 }
 
 /**
@@ -95,11 +91,7 @@ class ConfigurationOption(
      * Since initializers could potentially be empty, we make this nullable.
      */
     var value: Node? = null,
-) : Concept(underlyingNode = underlyingNode) {
-    init {
-        group.options.add(this)
-    }
-}
+) : Concept(underlyingNode = underlyingNode)
 
 /**
  * A common abstract class for configuration operations, such as reading options or a whole file.
@@ -124,11 +116,7 @@ class ReadConfigurationGroup(
     underlyingNode: Node,
     /** The config group that is being read with this operation. */
     var group: ConfigurationGroup,
-) : ConfigurationOperation(underlyingNode = underlyingNode, concept = group) {
-    init {
-        name = group.name
-    }
-}
+) : ConfigurationOperation(underlyingNode = underlyingNode, concept = group)
 
 /**
  * Represents an operation to read a specific configuration option. Often this is done with a member
@@ -138,11 +126,7 @@ class ReadConfigurationOption(
     underlyingNode: Node,
     /** The config option that is being read with this operation. */
     var option: ConfigurationOption,
-) : ConfigurationOperation(underlyingNode = underlyingNode, concept = option) {
-    init {
-        name = option.name
-    }
-}
+) : ConfigurationOperation(underlyingNode = underlyingNode, concept = option)
 
 /**
  * Represents an operation to register a new [ConfigurationGroup]. This is often done with a call,
@@ -158,11 +142,7 @@ class RegisterConfigurationGroup(
     underlyingNode: Node,
     /** The config group that is being registered with this operation. */
     var group: ConfigurationGroup,
-) : ConfigurationOperation(underlyingNode = underlyingNode, concept = group) {
-    init {
-        name = group.name
-    }
-}
+) : ConfigurationOperation(underlyingNode = underlyingNode, concept = group)
 
 /**
  * Represents an operation to register a new [ConfigurationOption]. This is often done with a call,
@@ -180,11 +160,7 @@ class RegisterConfigurationOption(
     var option: ConfigurationOption,
     /** An optional default value of the option. */
     var defaultValue: Node? = null,
-) : ConfigurationOperation(underlyingNode = underlyingNode, concept = option) {
-    init {
-        name = option.name
-    }
-}
+) : ConfigurationOperation(underlyingNode = underlyingNode, concept = option)
 
 /**
  * Represents an operation to provide a [Configuration], e.g., in the form of a configuration file
@@ -198,7 +174,7 @@ class RegisterConfigurationOption(
  * operation, such as environment variables or command-line arguments.
  *
  * Note: The [ProvideConfiguration] operation is part of the [ConfigurationSource.ops] and not of
- * the [Configuration.ops] as its an operation of the source, not the target.
+ * the [Configuration.ops] as it's an operation of the source, not the target.
  */
 class ProvideConfiguration(
     underlyingNode: Node,
@@ -214,11 +190,7 @@ class ProvideConfigurationGroup(
     underlyingNode: Node,
     var source: ConfigurationGroupSource,
     var group: ConfigurationGroup,
-) : ConfigurationOperation(underlyingNode = underlyingNode, concept = source) {
-    init {
-        name = group.name
-    }
-}
+) : ConfigurationOperation(underlyingNode = underlyingNode, concept = source)
 
 /**
  * Represents an operation to provide a [ConfigurationOption]. It connects a
@@ -229,11 +201,7 @@ class ProvideConfigurationOption(
     var source: ConfigurationOptionSource,
     var option: ConfigurationOption,
     var value: Node?,
-) : ConfigurationOperation(underlyingNode = underlyingNode, concept = source) {
-    init {
-        name = option.name
-    }
-}
+) : ConfigurationOperation(underlyingNode = underlyingNode, concept = source)
 
 /**
  * Represents a possible source for a configuration. For example, when loading an INI file with our
@@ -260,13 +228,8 @@ class ConfigurationSource(underlyingNode: Node) : Concept(underlyingNode = under
  * file with our INI file frontend, each section is presented as a [RecordDeclaration]. This record
  * declaration would be the source of the configuration group.
  */
-class ConfigurationGroupSource(underlyingNode: Node, conf: ConfigurationSource) :
-    Concept(underlyingNode = underlyingNode) {
+class ConfigurationGroupSource(underlyingNode: Node) : Concept(underlyingNode = underlyingNode) {
     val options: MutableList<ConfigurationOptionSource> = mutableListOf()
-
-    init {
-        conf.groups += this
-    }
 }
 
 /**
@@ -275,8 +238,4 @@ class ConfigurationGroupSource(underlyingNode: Node, conf: ConfigurationSource)
  * field declaration would be the source to the configuration option.
  */
 class ConfigurationOptionSource(underlyingNode: Node, var group: ConfigurationGroupSource) :
-    Concept(underlyingNode = underlyingNode) {
-    init {
-        group.options += this
-    }
-}
+    Concept(underlyingNode = underlyingNode)
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/ConfigurationBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/ConfigurationBuilder.kt
new file mode 100644
index 00000000000..ee630b6cad4
--- /dev/null
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/config/ConfigurationBuilder.kt
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2025, 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.concepts.config
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.concepts.newConcept
+import de.fraunhofer.aisec.cpg.graph.concepts.newOperation
+import de.fraunhofer.aisec.cpg.graph.statements.expressions.Expression
+
+/**
+ * Creates a new [Configuration] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [Configuration] concept.
+ */
+fun MetadataProvider.newConfiguration(underlyingNode: Node) =
+    newConcept(::Configuration, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [ConfigurationSource] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [ConfigurationSource] concept.
+ */
+fun MetadataProvider.newConfigurationSource(underlyingNode: Node) =
+    newConcept(::ConfigurationSource, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [ConfigurationGroupSource] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param concept The [ConfigurationSource] concept to which the group belongs.
+ * @return The created [ConfigurationGroupSource] concept.
+ */
+fun MetadataProvider.newConfigurationGroupSource(
+    underlyingNode: Node,
+    concept: ConfigurationSource,
+) =
+    newConcept({ ConfigurationGroupSource(it) }, underlyingNode = underlyingNode).apply {
+        concept.groups += this
+    }
+
+/**
+ * Creates a new [ConfigurationOptionSource] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param concept The [ConfigurationGroupSource] concept to which the operation belongs.
+ * @return The created [ConfigurationOptionSource] concept.
+ */
+fun MetadataProvider.newConfigurationOptionSource(
+    underlyingNode: Node,
+    concept: ConfigurationGroupSource,
+) =
+    newConcept({ ConfigurationOptionSource(it, concept) }, underlyingNode = underlyingNode).apply {
+        concept.options += this
+    }
+
+/**
+ * Creates a new [ConfigurationGroup] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param concept The [Configuration] concept to which the group belongs.
+ * @return The created [ConfigurationGroup] concept.
+ */
+fun MetadataProvider.newConfigurationGroup(underlyingNode: Node, concept: Configuration) =
+    newConcept(
+            { ConfigurationGroup(underlyingNode = it, conf = concept) },
+            underlyingNode = underlyingNode,
+        )
+        .apply { concept.groups += this }
+
+/**
+ * Creates a new [ConfigurationOption] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param concept The [ConfigurationGroup] concept to which the option belongs.
+ * @param key The key node for the configuration option.
+ * @param value The value node for the configuration option.
+ * @return The created [ConfigurationOption] concept.
+ */
+fun MetadataProvider.newConfigurationOption(
+    underlyingNode: Node,
+    concept: ConfigurationGroup,
+    key: Node,
+    value: Node?,
+) =
+    newConcept(
+            { ConfigurationOption(underlyingNode = it, group = concept, key = key, value = value) },
+            underlyingNode = underlyingNode,
+        )
+        .apply { concept.options += this }
+
+/**
+ * Creates a new [LoadConfiguration] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [Configuration] concept to which the load operation belongs.
+ * @param fileExpression The expression representing the file to load.
+ * @return The created [LoadConfiguration] operation.
+ */
+fun MetadataProvider.newLoadConfiguration(
+    underlyingNode: Node,
+    concept: Configuration,
+    fileExpression: Expression,
+) =
+    newOperation(
+        { underlyingNode, concept ->
+            LoadConfiguration(
+                underlyingNode = underlyingNode,
+                conf = concept,
+                fileExpression = fileExpression,
+            )
+        },
+        underlyingNode = underlyingNode,
+        concept = concept,
+    )
+
+/**
+ * Creates a new [ReadConfigurationGroup] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [ConfigurationGroup] concept to which the read operation belongs.
+ * @return The created [ReadConfigurationGroup] operation.
+ */
+fun MetadataProvider.newReadConfigurationGroup(underlyingNode: Node, concept: ConfigurationGroup) =
+    newOperation(
+            { underlyingNode, concept ->
+                ReadConfigurationGroup(underlyingNode = underlyingNode, group = concept)
+            },
+            underlyingNode = underlyingNode,
+            concept = concept,
+        )
+        .apply { name = group.name }
+
+/**
+ * Creates a new [RegisterConfigurationGroup] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [ConfigurationGroup] concept to which the register operation belongs.
+ * @return The created [RegisterConfigurationGroup] operation.
+ */
+fun MetadataProvider.newRegisterConfigurationGroup(
+    underlyingNode: Node,
+    concept: ConfigurationGroup,
+) =
+    newOperation(
+            { underlyingNode, concept ->
+                RegisterConfigurationGroup(underlyingNode = underlyingNode, group = concept)
+            },
+            underlyingNode = underlyingNode,
+            concept = concept,
+        )
+        .apply { name = group.name }
+
+/**
+ * Creates a new [ProvideConfiguration] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param conf The [Configuration] concept which is the target which is provided by this operation.
+ * @param source The [ConfigurationSource] concept to which the provide operation belongs and the
+ *   source of the [Configuration].
+ * @return The created [ProvideConfiguration] operation.
+ */
+fun MetadataProvider.newProvideConfiguration(
+    underlyingNode: Node,
+    conf: Configuration,
+    source: ConfigurationSource,
+) =
+    newOperation(
+        { underlyingNode, concept ->
+            ProvideConfiguration(underlyingNode = underlyingNode, source = source, conf = conf)
+        },
+        underlyingNode = underlyingNode,
+        concept = source,
+    )
+
+/**
+ * Creates a new [ProvideConfigurationGroup] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param group The [ConfigurationGroup] concept which is the target which is provided by this
+ *   operation.
+ * @param source The [ConfigurationSource] concept to which the provide operation belongs and the
+ *   source of the [ConfigurationGroup].
+ * @return The created [ProvideConfigurationGroup] operation.
+ */
+fun MetadataProvider.newProvideConfigurationGroup(
+    underlyingNode: Node,
+    group: ConfigurationGroup,
+    source: ConfigurationGroupSource,
+) =
+    newOperation(
+            { underlyingNode, concept ->
+                ProvideConfigurationGroup(
+                    underlyingNode = underlyingNode,
+                    source = source,
+                    group = group,
+                )
+            },
+            underlyingNode = underlyingNode,
+            concept = source,
+        )
+        .apply { name = concept.name }
+
+/**
+ * Creates a new [ReadConfigurationOption] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [ConfigurationOption] concept to which the read operation belongs.
+ * @return The created [ReadConfigurationOption] operation.
+ */
+fun MetadataProvider.newReadConfigurationOption(
+    underlyingNode: Node,
+    concept: ConfigurationOption,
+) =
+    newOperation(
+            { underlyingNode, concept ->
+                ReadConfigurationOption(underlyingNode = underlyingNode, option = concept)
+            },
+            underlyingNode = underlyingNode,
+            concept = concept,
+        )
+        .apply { name = option.name }
+
+/**
+ * Creates a new [RegisterConfigurationOption] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [ConfigurationOption] concept to which the register operation belongs.
+ * @param defaultValue The default value for the configuration option.
+ * @return The created [RegisterConfigurationOption] operation.
+ */
+fun MetadataProvider.newRegisterConfigurationOption(
+    underlyingNode: Node,
+    concept: ConfigurationOption,
+    defaultValue: Node?,
+) =
+    newOperation(
+            { underlyingNode, concept ->
+                RegisterConfigurationOption(
+                    underlyingNode = underlyingNode,
+                    option = concept,
+                    defaultValue = defaultValue,
+                )
+            },
+            underlyingNode = underlyingNode,
+            concept = concept,
+        )
+        .apply { name = option.name }
+
+/**
+ * Creates a new [ProvideConfigurationOption] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param option The [ConfigurationOption] concept which is provided by this operation.
+ * @param source The [ConfigurationOptionSource] representing the concept to which this operation
+ *   belongs and the source providing this [ConfigurationOption].
+ * @param value The value of the configuration option.
+ * @return The created [ProvideConfigurationOption] operation.
+ */
+fun MetadataProvider.newProvideConfigurationOption(
+    underlyingNode: Node,
+    option: ConfigurationOption,
+    source: ConfigurationOptionSource,
+    value: Node?,
+) =
+    newOperation(
+            { underlyingNode, concept ->
+                ProvideConfigurationOption(
+                    underlyingNode = underlyingNode,
+                    source = source,
+                    option = option,
+                    value = value,
+                )
+            },
+            underlyingNode = underlyingNode,
+            concept = source,
+        )
+        .apply { name = concept.name }
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/diskEncryption/DiskEncryptionBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/diskEncryption/DiskEncryptionBuilder.kt
new file mode 100644
index 00000000000..06be38f7a5b
--- /dev/null
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/diskEncryption/DiskEncryptionBuilder.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2025, 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.concepts.diskEncryption
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.concepts.newConcept
+import de.fraunhofer.aisec.cpg.graph.concepts.newOperation
+
+/**
+ * Creates a new [DiskEncryption] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param cipher The cipher used for encryption.
+ * @param key The secret key used for encryption.
+ * @return The created [DiskEncryption] concept.
+ */
+fun MetadataProvider.newDiskEncryption(underlyingNode: Node, cipher: Cipher?, key: Secret?) =
+    newConcept(
+        {
+            val node = DiskEncryption(it)
+            key?.let { node.key = it }
+            cipher?.let { node.cipher = it }
+            node
+        },
+        underlyingNode = underlyingNode,
+    )
+
+/**
+ * Creates a new [Cipher] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [Cipher] concept.
+ */
+fun MetadataProvider.newCipher(underlyingNode: Node) =
+    newConcept(::Cipher, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [Secret] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [Secret] concept.
+ */
+fun MetadataProvider.newSecret(underlyingNode: Node) =
+    newConcept(::Secret, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [BlockStorage] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [BlockStorage] concept.
+ */
+fun MetadataProvider.newBlockStorage(underlyingNode: Node) =
+    newConcept(::BlockStorage, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [Encrypt] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [Cipher] concept to which the operation belongs.
+ * @param key The secret key used for encryption.
+ * @return The created [Encrypt] operation.
+ */
+fun MetadataProvider.newEncryptOperation(underlyingNode: Node, concept: Cipher, key: Secret) =
+    newOperation(
+        { node, concept -> Encrypt(underlyingNode, concept, key) },
+        underlyingNode = underlyingNode,
+        concept = concept,
+    )
+
+/**
+ * Creates a new [CreateEncryptedDisk] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [DiskEncryption] concept to which the operation belongs.
+ * @return The created [CreateEncryptedDisk] operation.
+ */
+fun MetadataProvider.newCreateEncryptedDisk(underlyingNode: Node, concept: DiskEncryption) =
+    newOperation(::CreateEncryptedDisk, underlyingNode = underlyingNode, concept = concept)
+
+/**
+ * Creates a new [UnlockEncryptedDisk] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [DiskEncryption] concept to which the operation belongs.
+ * @return The created [UnlockEncryptedDisk] operation.
+ */
+fun MetadataProvider.newUnlockEncryptedDisk(underlyingNode: Node, concept: DiskEncryption) =
+    newOperation(::UnlockEncryptedDisk, underlyingNode = underlyingNode, concept = concept)
+
+/**
+ * Creates a new [CreateSecret] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [Secret] concept to which the operation belongs.
+ * @return The created [CreateSecret] operation.
+ */
+fun MetadataProvider.newCreateSecret(underlyingNode: Node, concept: Secret) =
+    newOperation(::CreateSecret, underlyingNode = underlyingNode, concept = concept)
+
+/**
+ * Creates a new [GetSecret] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [Secret] concept to which the operation belongs.
+ * @return The created [GetSecret] operation.
+ */
+fun MetadataProvider.newGetSecret(underlyingNode: Node, concept: Secret) =
+    newOperation(::GetSecret, underlyingNode = underlyingNode, concept = concept)
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/file/FileBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/file/FileBuilder.kt
index 6ab81a27e45..158772ed3e3 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/file/FileBuilder.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/file/FileBuilder.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved.
+ * Copyright (c) 2025, 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.
@@ -27,8 +27,8 @@ package de.fraunhofer.aisec.cpg.graph.concepts.file
 
 import de.fraunhofer.aisec.cpg.graph.MetadataProvider
 import de.fraunhofer.aisec.cpg.graph.Node
-import de.fraunhofer.aisec.cpg.graph.NodeBuilder
-import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.concepts.newConcept
+import de.fraunhofer.aisec.cpg.graph.concepts.newOperation
 
 /**
  * Creates a new [File] node. This node represents a file on a hard-disk somewhere.
@@ -37,13 +37,11 @@ import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
  * @param fileName The name of the file e.g. `foo/bar/example.txt`
  * @return The new [File] node.
  */
-fun MetadataProvider.newFile(underlyingNode: Node, fileName: String): File {
-    val node = File(underlyingNode = underlyingNode, fileName = fileName)
-    node.codeAndLocationFrom(underlyingNode)
-
-    NodeBuilder.log(node)
-    return node
-}
+fun MetadataProvider.newFile(underlyingNode: Node, fileName: String) =
+    newConcept(
+        { File(underlyingNode = underlyingNode, fileName = fileName) },
+        underlyingNode = underlyingNode,
+    )
 
 /**
  * Creates a new [OpenFile] node. This node represents opening a file.
@@ -52,13 +50,12 @@ fun MetadataProvider.newFile(underlyingNode: Node, fileName: String): File {
  * @param file The [File] this operation is opening.
  * @return The new [OpenFile] node.
  */
-fun MetadataProvider.newFileOpen(underlyingNode: Node, file: File): OpenFile {
-    val node = OpenFile(underlyingNode = underlyingNode, concept = file)
-    node.codeAndLocationFrom(underlyingNode)
-
-    NodeBuilder.log(node)
-    return node
-}
+fun MetadataProvider.newFileOpen(underlyingNode: Node, file: File) =
+    newOperation(
+        { underlyingNode, concept -> OpenFile(underlyingNode = underlyingNode, concept = file) },
+        underlyingNode = underlyingNode,
+        concept = file,
+    )
 
 /**
  * Creates a new [SetFileMask] node. This node represents changing a files permissions.
@@ -68,13 +65,14 @@ fun MetadataProvider.newFileOpen(underlyingNode: Node, file: File): OpenFile {
  * @param mask The file mask to set (in UNIX notation).
  * @return The new [SetFileMask] node.
  */
-fun MetadataProvider.newFileSetMask(underlyingNode: Node, file: File, mask: Long): SetFileMask {
-    val node = SetFileMask(underlyingNode = underlyingNode, concept = file, mask = mask)
-    node.codeAndLocationFrom(underlyingNode)
-
-    NodeBuilder.log(node)
-    return node
-}
+fun MetadataProvider.newFileSetMask(underlyingNode: Node, file: File, mask: Long) =
+    newOperation(
+        { underlyingNode, concept ->
+            SetFileMask(underlyingNode = underlyingNode, concept = concept, mask = mask)
+        },
+        underlyingNode = underlyingNode,
+        concept = file,
+    )
 
 /**
  * Creates a new [SetFileFlags] node.
@@ -88,13 +86,14 @@ fun MetadataProvider.newFileSetFlags(
     underlyingNode: Node,
     file: File,
     flags: Set<FileAccessModeFlags>,
-): SetFileFlags {
-    val node = SetFileFlags(underlyingNode = underlyingNode, concept = file, flags = flags)
-    node.codeAndLocationFrom(underlyingNode)
-
-    NodeBuilder.log(node)
-    return node
-}
+) =
+    newOperation(
+        { underlyingNode, concept ->
+            SetFileFlags(underlyingNode = underlyingNode, concept = file, flags = flags)
+        },
+        underlyingNode = underlyingNode,
+        concept = file,
+    )
 
 /**
  * Creates a new [CloseFile] node.
@@ -103,13 +102,12 @@ fun MetadataProvider.newFileSetFlags(
  * @param file The [File] this operation is closing.
  * @return The new [CloseFile] node.
  */
-fun MetadataProvider.newFileClose(underlyingNode: Node, file: File): CloseFile {
-    val node = CloseFile(underlyingNode = underlyingNode, concept = file)
-    node.codeAndLocationFrom(underlyingNode)
-
-    NodeBuilder.log(node)
-    return node
-}
+fun MetadataProvider.newFileClose(underlyingNode: Node, file: File) =
+    newOperation(
+        { underlyingNode, concept -> CloseFile(underlyingNode = underlyingNode, concept = file) },
+        underlyingNode = underlyingNode,
+        concept = file,
+    )
 
 /**
  * Creates a new [DeleteFile] node.
@@ -118,13 +116,12 @@ fun MetadataProvider.newFileClose(underlyingNode: Node, file: File): CloseFile {
  * @param file The [File] this operation is deleting.
  * @return The new [DeleteFile] node.
  */
-fun MetadataProvider.newFileDelete(underlyingNode: Node, file: File): DeleteFile {
-    val node = DeleteFile(underlyingNode = underlyingNode, concept = file)
-    node.codeAndLocationFrom(underlyingNode)
-
-    NodeBuilder.log(node)
-    return node
-}
+fun MetadataProvider.newFileDelete(underlyingNode: Node, file: File) =
+    newOperation(
+        { underlyingNode, concept -> DeleteFile(underlyingNode = underlyingNode, concept = file) },
+        underlyingNode = underlyingNode,
+        concept = file,
+    )
 
 /**
  * Creates a new [ReadFile] node and attaches the DFG from the corresponding [file] to the new node
@@ -134,17 +131,18 @@ fun MetadataProvider.newFileDelete(underlyingNode: Node, file: File): DeleteFile
  * @param file The [File] this operation is reading from.
  * @return The new [ReadFile] node.
  */
-fun MetadataProvider.newFileRead(underlyingNode: Node, file: File): ReadFile {
-    val node = ReadFile(underlyingNode = underlyingNode, concept = file)
-    node.codeAndLocationFrom(underlyingNode)
-
-    // add DFG
-    file.nextDFG += node
-    node.nextDFG += underlyingNode
-
-    NodeBuilder.log(node)
-    return node
-}
+fun MetadataProvider.newFileRead(underlyingNode: Node, file: File) =
+    newOperation(
+            { underlyingNode, concept ->
+                ReadFile(underlyingNode = underlyingNode, concept = file)
+            },
+            underlyingNode = underlyingNode,
+            concept = file,
+        )
+        .apply {
+            file.nextDFG += this
+            this.nextDFG += underlyingNode
+        }
 
 /**
  * Creates a new [WriteFile] node and attaches the DFG from [what] to [this] and then from the new
@@ -155,14 +153,15 @@ fun MetadataProvider.newFileRead(underlyingNode: Node, file: File): ReadFile {
  * @param what A node being written to the [file] (usually the argument of a `write` call).
  * @return The new [WriteFile] node.
  */
-fun MetadataProvider.newFileWrite(underlyingNode: Node, file: File, what: Node): WriteFile {
-    val node = WriteFile(underlyingNode = underlyingNode, concept = file, what = what)
-    node.codeAndLocationFrom(underlyingNode)
-
-    // add DFG
-    what.nextDFG += node
-    node.nextDFG += file
-
-    NodeBuilder.log(node)
-    return node
-}
+fun MetadataProvider.newFileWrite(underlyingNode: Node, file: File, what: Node) =
+    newOperation(
+            { underlyingNode, concept ->
+                WriteFile(underlyingNode = underlyingNode, concept = file, what = what)
+            },
+            underlyingNode = underlyingNode,
+            concept = file,
+        )
+        .apply {
+            what.nextDFG += this
+            this.nextDFG += file
+        }
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/flows/EntryPointBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/flows/EntryPointBuilder.kt
new file mode 100644
index 00000000000..3683c4e488e
--- /dev/null
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/flows/EntryPointBuilder.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2025, 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.concepts.flows
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.concepts.arch.OperatingSystemArchitecture
+import de.fraunhofer.aisec.cpg.graph.concepts.newConcept
+import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
+
+/**
+ * Creates a new [Main] concept.
+ *
+ * @param underlyingNode The underlying [FunctionDeclaration] representing the main method.
+ * @param os If this entry point is specifically designed to be invoked on a certain
+ *   [OperatingSystemArchitecture], it can be specified here.
+ * @return The created [Main] concept.
+ */
+fun MetadataProvider.newMain(underlyingNode: FunctionDeclaration, os: OperatingSystemArchitecture) =
+    newConcept({ Main(underlyingNode = underlyingNode, os = os) }, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [LibraryEntryPoint] concept.
+ *
+ * @param underlyingNode The underlying [FunctionDeclaration] representing the main method.
+ * @param os If this entry point is specifically designed to be invoked on a certain
+ *   [OperatingSystemArchitecture], it can be specified here.
+ * @return The created [LibraryEntryPoint] concept.
+ */
+fun MetadataProvider.newLibraryEntryPoint(
+    underlyingNode: FunctionDeclaration,
+    os: OperatingSystemArchitecture,
+) =
+    newConcept(
+        { LibraryEntryPoint(underlyingNode = underlyingNode, os = os) },
+        underlyingNode = underlyingNode,
+    )
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/http/HttpConceptBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/http/HttpConceptBuilder.kt
new file mode 100644
index 00000000000..05f11d9d686
--- /dev/null
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/http/HttpConceptBuilder.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2025, 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.concepts.http
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.concepts.Concept
+import de.fraunhofer.aisec.cpg.graph.concepts.auth.Authentication
+import de.fraunhofer.aisec.cpg.graph.concepts.newConcept
+import de.fraunhofer.aisec.cpg.graph.concepts.newOperation
+import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
+
+/**
+ * Creates a new [HttpClient] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param isTLS Whether the client uses TLS.
+ * @param authentication The [Authentication] method used by the client.
+ * @return The created [HttpClient] concept.
+ */
+fun MetadataProvider.newHttpClient(
+    underlyingNode: Node,
+    isTLS: Boolean?,
+    authentication: Authentication?,
+) =
+    newConcept(
+        { HttpClient(it, isTLS = isTLS, authentication = authentication) },
+        underlyingNode = underlyingNode,
+    )
+
+/**
+ * Creates a new [HttpRequestHandler] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param basePath The base path of the [HttpRequestHandler].
+ * @param endpoints A list of the [HttpEndpoint]s exposed by the [HttpRequestHandler].
+ * @return The created [HttpRequestHandler] concept.
+ */
+fun MetadataProvider.newHttpRequestHandler(
+    underlyingNode: Node,
+    basePath: String,
+    endpoints: MutableList<HttpEndpoint>,
+) =
+    newConcept(
+        { HttpRequestHandler(it, basePath = basePath, endpoints = endpoints) },
+        underlyingNode = underlyingNode,
+    )
+
+/**
+ * Creates a new [HttpEndpoint] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param httpMethod The [HttpMethod] the created [HttpEndpoint] listens to.
+ * @param path The path of the created [HttpEndpoint].
+ * @param arguments A list of the [Node]s representing the arguments passed to the [HttpEndpoint].
+ * @param authentication The [Authentication] method used by the [HttpEndpoint].
+ * @return The created [HttpEndpoint] concept.
+ */
+fun MetadataProvider.newHttpEndpoint(
+    underlyingNode: FunctionDeclaration,
+    httpMethod: HttpMethod,
+    path: String,
+    arguments: List<Node>,
+    authentication: Authentication?,
+) =
+    newConcept(
+        {
+            HttpEndpoint(
+                underlyingNode = underlyingNode,
+                httpMethod = httpMethod,
+                path = path,
+                arguments = arguments,
+                authentication = authentication,
+            )
+        },
+        underlyingNode = underlyingNode,
+    )
+
+/**
+ * Creates a new [HttpRequest] operation for the given [HttpEndpoint].
+ *
+ * @param underlyingNode The underlying [Node] representing the request.
+ * @param concept The [HttpClient] concept this operation belongs to.
+ * @return The created [HttpRequest] concept.
+ */
+fun MetadataProvider.newHttpRequest(
+    underlyingNode: Node,
+    concept: HttpClient,
+    url: String,
+    arguments: List<Node>,
+    httpMethod: HttpMethod,
+) =
+    newOperation(
+        { underlyingNode, concept ->
+            HttpRequest(
+                underlyingNode = underlyingNode,
+                url = url,
+                arguments = arguments,
+                httpMethod = httpMethod,
+                concept = concept,
+            )
+        },
+        underlyingNode = underlyingNode,
+        concept = concept,
+    )
+
+/**
+ * Creates a new [RegisterHttpEndpoint] operation for the given [HttpEndpoint].
+ *
+ * @param underlyingNode The underlying [Node] registering the endpoint method.
+ * @param concept The [Concept] to which this operation belongs.
+ * @param httpEndpoint The [HttpEndpoint] which is registered by this operation.
+ * @return The created [RegisterHttpEndpoint] concept.
+ */
+fun MetadataProvider.newRegisterHttpEndpoint(
+    underlyingNode: Node,
+    concept: Concept,
+    httpEndpoint: HttpEndpoint,
+) =
+    newOperation(
+        { underlyingNode, concept ->
+            RegisterHttpEndpoint(
+                underlyingNode = underlyingNode,
+                concept = concept,
+                httpEndpoint = httpEndpoint,
+            )
+        },
+        underlyingNode = underlyingNode,
+        concept = concept,
+    )
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/logging/Log.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/logging/Log.kt
index e47e3fb923c..cf0dcbd3edd 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/logging/Log.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/logging/Log.kt
@@ -33,4 +33,6 @@ import de.fraunhofer.aisec.cpg.graph.concepts.Concept
  *
  * @param underlyingNode The underlying CPG node.
  */
-class Log(underlyingNode: Node) : Concept(underlyingNode = underlyingNode), IsLogging
+class Log(underlyingNode: Node) : Concept(underlyingNode = underlyingNode), IsLogging {
+    var logName: String? = null
+}
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/logging/LoggingBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/logging/LoggingBuilder.kt
index f60f56c07fc..ca999d3a5be 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/logging/LoggingBuilder.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/logging/LoggingBuilder.kt
@@ -27,8 +27,8 @@ package de.fraunhofer.aisec.cpg.graph.concepts.logging
 
 import de.fraunhofer.aisec.cpg.graph.MetadataProvider
 import de.fraunhofer.aisec.cpg.graph.Node
-import de.fraunhofer.aisec.cpg.graph.NodeBuilder
-import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.concepts.newConcept
+import de.fraunhofer.aisec.cpg.graph.concepts.newOperation
 
 /**
  * Creates a [Log] with the same metadata as the [underlyingNode].
@@ -37,13 +37,8 @@ import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
  * @param name The name of the logger.
  * @return The new [Log].
  */
-fun MetadataProvider.newLog(underlyingNode: Node, name: String): Log {
-    val node = Log(underlyingNode = underlyingNode)
-    node.codeAndLocationFrom(underlyingNode)
-
-    NodeBuilder.log(node)
-    return node
-}
+fun MetadataProvider.newLog(underlyingNode: Node, name: String) =
+    newConcept(::Log, underlyingNode).apply { this.logName = name }
 
 /**
  * Creates a [LogWrite] node with the same metadata as the [underlyingNode].
@@ -53,48 +48,41 @@ fun MetadataProvider.newLog(underlyingNode: Node, name: String): Log {
  * given log" or "is the sensitive data flowing to a log".
  *
  * @param underlyingNode The underlying CPG node (e.g. a call expression writing to a log).
+ * @param concept The [Log] concept this operation belongs to.
  * @param level The [LogLevel] used for this write operation.
- * @param logger The corresponding [Log], i.e. the log where the underlying nodes is writing to.
  * @param logArguments The underlying CPG nodes of the logging arguments, i.e. what is written to
  *   the log.
  * @return The new [Log].
  */
 fun MetadataProvider.newLogWrite(
     underlyingNode: Node,
+    concept: Log,
     level: LogLevel,
-    logger: Log,
     logArguments: List<Node>,
-): LogWrite {
-    val node =
-        LogWrite(
+) =
+    newOperation(
+            { node, concept ->
+                LogWrite(
+                    underlyingNode = underlyingNode,
+                    concept = concept,
+                    logArguments = logArguments,
+                    logLevel = level,
+                )
+            },
             underlyingNode = underlyingNode,
-            concept = logger,
-            logArguments = logArguments,
-            logLevel = level,
+            concept = concept,
         )
-    node.codeAndLocationFrom(underlyingNode)
-
-    logger.ops += node
-
-    // connect DFG
-    logArguments.forEach { cpgArgNode -> cpgArgNode.nextDFG += node }
-    node.nextDFG += logger
-
-    NodeBuilder.log(node)
-    return node
-}
+        .apply {
+            this.nextDFG += concept
+            this.prevDFG += logArguments
+        }
 
 /**
  * Creates a [LogGet] node with the same metadata as the [underlyingNode].
  *
  * @param underlyingNode The underlying CPG node (e.g. a call expression writing to a log).
- * @param logger The corresponding [Log], i.e. the log where the underlying nodes is writing to.
+ * @param concept The [Log] concept this operation belongs to.
  * @return The new [LogGet].
  */
-fun MetadataProvider.newLogGet(underlyingNode: Node, logger: Log): LogGet {
-    val node = LogGet(underlyingNode = underlyingNode, concept = logger)
-    node.codeAndLocationFrom(underlyingNode)
-
-    NodeBuilder.log(node)
-    return node
-}
+fun MetadataProvider.newLogGet(underlyingNode: Node, concept: Log) =
+    newOperation(::LogGet, underlyingNode = underlyingNode, concept = concept)
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/memory/DynamicLoading.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/memory/DynamicLoading.kt
index 20411f0dc06..380b33f3ddb 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/memory/DynamicLoading.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/memory/DynamicLoading.kt
@@ -120,7 +120,7 @@ class LoadSymbol<T : Declaration>(
      * If this operation is targeting a specific [OperatingSystemArchitecture], it can be specified
      * here.
      */
-    os: OperatingSystemArchitecture? = loader?.os,
+    os: OperatingSystemArchitecture?,
 ) :
     DynamicLoadingOperation<T>(
         underlyingNode = underlyingNode,
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/memory/MemoryConceptBuilder.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/memory/MemoryConceptBuilder.kt
new file mode 100644
index 00000000000..1a1e5b82a66
--- /dev/null
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/memory/MemoryConceptBuilder.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2025, 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.concepts.memory
+
+import de.fraunhofer.aisec.cpg.graph.Component
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.concepts.Concept
+import de.fraunhofer.aisec.cpg.graph.concepts.arch.OperatingSystemArchitecture
+import de.fraunhofer.aisec.cpg.graph.concepts.flows.LibraryEntryPoint
+import de.fraunhofer.aisec.cpg.graph.concepts.newConcept
+import de.fraunhofer.aisec.cpg.graph.concepts.newOperation
+import de.fraunhofer.aisec.cpg.graph.declarations.Declaration
+
+/**
+ * Creates a new [Memory] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @param mode The [MemoryManagementMode] which is used to manage the memory.
+ * @return The created [Memory] concept.
+ */
+fun MetadataProvider.newMemory(underlyingNode: Node, mode: MemoryManagementMode) =
+    newConcept({ Memory(it, mode = mode) }, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [DynamicLoading] concept.
+ *
+ * @param underlyingNode The underlying node representing this concept.
+ * @return The created [DynamicLoading] concept.
+ */
+fun MetadataProvider.newDynamicLoading(underlyingNode: Node) =
+    newConcept(::DynamicLoading, underlyingNode = underlyingNode)
+
+/**
+ * Creates a new [Allocate] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ *     * @param concept The [Concept] concept this operation belongs to.
+ *
+ * @param what Defines the object whose memory is allocated.
+ * @return The created [Allocate] concept.
+ */
+fun MetadataProvider.newAllocate(underlyingNode: Node, concept: Concept, what: Node?) =
+    newOperation(
+        { underlyingNode, concept ->
+            Allocate(underlyingNode = underlyingNode, concept = concept, what = what)
+        },
+        underlyingNode = underlyingNode,
+        concept = concept,
+    )
+
+/**
+ * Creates a new [DeAllocate] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ *     * @param concept The [Concept] concept this operation belongs to.
+ *
+ * @param what Defines the object whose memory is deallocated.
+ * @return The created [DeAllocate] concept.
+ */
+fun MetadataProvider.newDeallocate(underlyingNode: Node, concept: Concept, what: Node?) =
+    newOperation(
+        { underlyingNode, concept ->
+            DeAllocate(underlyingNode = underlyingNode, concept = concept, what = what)
+        },
+        underlyingNode = underlyingNode,
+        concept = concept,
+    )
+
+/**
+ * Creates a new [LoadLibrary] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ *     * @param concept The [Concept] concept this operation belongs to.
+ *
+ * @param what Defines which component is loaded.
+ * @param entryPoints A list of the entry points of the library.
+ * @param os The operating system architecture. Can be `null`.
+ * @return The created [LoadLibrary] concept.
+ */
+fun MetadataProvider.newLoadLibrary(
+    underlyingNode: Node,
+    concept: Concept,
+    what: Component?,
+    entryPoints: List<LibraryEntryPoint>,
+    os: OperatingSystemArchitecture?,
+) =
+    newOperation(
+        { underlyingNode, concept ->
+            LoadLibrary(
+                underlyingNode = underlyingNode,
+                concept = concept,
+                what = what,
+                entryPoints = entryPoints,
+                os = os,
+            )
+        },
+        underlyingNode = underlyingNode,
+        concept = concept,
+    )
+
+/**
+ * Creates a new [LoadSymbol] operation.
+ *
+ * @param underlyingNode The underlying node representing this operation.
+ * @param concept The [Concept] concept this operation belongs to.
+ * @param what Defines which symbol is loaded.
+ * @param loader If we are loading a symbol from an external library, this points to the
+ *   [LoadLibrary] operation that loaded the library. Can be `null`.
+ * @param os The operating system architecture. Can be `null`.
+ * @return The created [LoadSymbol] concept.
+ */
+fun <T : Declaration> MetadataProvider.newLoadSymbol(
+    underlyingNode: Node,
+    concept: Concept,
+    what: T?,
+    loader: LoadLibrary?,
+    os: OperatingSystemArchitecture?,
+) =
+    newOperation(
+        { underlyingNode, concept ->
+            LoadSymbol<T>(
+                underlyingNode = underlyingNode,
+                concept = concept,
+                what = what,
+                loader = loader,
+                os = os,
+            )
+        },
+        underlyingNode = underlyingNode,
+        concept = concept,
+    )
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ProvideConfigPass.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ProvideConfigPass.kt
index 81703928a3b..82079e800f3 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ProvideConfigPass.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ProvideConfigPass.kt
@@ -36,8 +36,9 @@ import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationOptionSource
 import de.fraunhofer.aisec.cpg.graph.concepts.config.ConfigurationSource
 import de.fraunhofer.aisec.cpg.graph.concepts.config.LoadConfiguration
 import de.fraunhofer.aisec.cpg.graph.concepts.config.ProvideConfiguration
-import de.fraunhofer.aisec.cpg.graph.concepts.config.ProvideConfigurationGroup
-import de.fraunhofer.aisec.cpg.graph.concepts.config.ProvideConfigurationOption
+import de.fraunhofer.aisec.cpg.graph.concepts.config.newProvideConfiguration
+import de.fraunhofer.aisec.cpg.graph.concepts.config.newProvideConfigurationGroup
+import de.fraunhofer.aisec.cpg.graph.concepts.config.newProvideConfigurationOption
 import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
 import de.fraunhofer.aisec.cpg.graph.operationNodes
 import de.fraunhofer.aisec.cpg.graph.translationResult
@@ -72,7 +73,8 @@ class ProvideConfigPass(ctx: TranslationContext) : ConceptPass(ctx) {
                     }
 
             // And create a ProvideConfiguration node for each of them
-            loadConfigOps?.flatMap { handleConfiguration(source, it.conf, tu, it) } ?: listOf()
+            loadConfigOps?.forEach { handleConfiguration(source, it.conf, tu, it) }
+            listOf()
         }
     }
 
@@ -86,11 +88,10 @@ class ProvideConfigPass(ctx: TranslationContext) : ConceptPass(ctx) {
 
         // Loop through all groups and options and create ProvideConfigurationGroup and
         // ProvideConfigurationOption nodes
-        ops += source.groups.mapNotNull { handleConfigurationGroup(conf, it) }.flatten()
+        source.groups.mapNotNull { handleConfigurationGroup(conf, it) }.flatten()
 
-        ops +=
-            ProvideConfiguration(underlyingNode = tu, conf = configuration.conf, source = source)
-                .also { it.name = source.name }
+        newProvideConfiguration(underlyingNode = tu, conf = configuration.conf, source = source)
+            .also { it.name = source.name }
 
         return ops
     }
@@ -113,20 +114,18 @@ class ProvideConfigPass(ctx: TranslationContext) : ConceptPass(ctx) {
             return null
         }
 
-        val op =
-            ProvideConfigurationGroup(
-                    underlyingNode = sourceUnderlying,
-                    group = group,
-                    source = source,
-                )
-                .also { it.name = source.name }
-        ops += op
+        newProvideConfigurationGroup(
+                underlyingNode = sourceUnderlying,
+                group = group,
+                source = source,
+            )
+            .also { it.name = source.name }
 
         // Add an incoming DFG edge from the source
         group.prevDFGEdges.add(source)
 
         // Continue with the options
-        ops += source.options.mapNotNull { handleConfigurationOption(group, it) }
+        source.options.forEach { handleConfigurationOption(group, it) }
 
         return ops
     }
@@ -134,8 +133,8 @@ class ProvideConfigPass(ctx: TranslationContext) : ConceptPass(ctx) {
     private fun handleConfigurationOption(
         group: ConfigurationGroup,
         source: ConfigurationOptionSource,
-    ): ConfigurationOperation? {
-        val sourceUnderlying = source.underlyingNode ?: return null
+    ) {
+        val sourceUnderlying = source.underlyingNode ?: return
         val option = group.options.singleOrNull { it.name.localName == source.name.localName }
 
         if (option == null) {
@@ -145,21 +144,18 @@ class ProvideConfigPass(ctx: TranslationContext) : ConceptPass(ctx) {
                 "Could not find configuration option for source {}",
                 sourceUnderlying.name.localName,
             )
-            return null
+            return
         }
 
-        val op =
-            ProvideConfigurationOption(
-                    underlyingNode = sourceUnderlying,
-                    option = option,
-                    value = sourceUnderlying,
-                    source = source,
-                )
-                .also { it.name = source.name }
+        newProvideConfigurationOption(
+                underlyingNode = sourceUnderlying,
+                option = option,
+                value = sourceUnderlying,
+                source = source,
+            )
+            .also { it.name = source.name }
 
         // Add an incoming DFG edge from the source
         option.prevDFGEdges.add(source)
-
-        return op
     }
 }
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationSourcePass.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationSourcePass.kt
index 7aa61f7e4c5..016612e2cc0 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationSourcePass.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/ini/IniFileConfigurationSourcePass.kt
@@ -61,7 +61,7 @@ class IniFileConfigurationSourcePass(ctx: TranslationContext) : ConceptPass(ctx)
     }
 
     private fun handleTranslationUnit(tu: TranslationUnitDeclaration): ConfigurationSource {
-        return ConfigurationSource(underlyingNode = tu).also { it.name = tu.name }
+        return newConfigurationSource(underlyingNode = tu).also { it.name = tu.name }
     }
 
     /**
@@ -84,7 +84,7 @@ class IniFileConfigurationSourcePass(ctx: TranslationContext) : ConceptPass(ctx)
         }
 
         val group =
-            ConfigurationGroupSource(underlyingNode = record, conf = conf).also {
+            newConfigurationGroupSource(underlyingNode = record, concept = conf).also {
                 it.name = record.name
             }
 
@@ -116,7 +116,7 @@ class IniFileConfigurationSourcePass(ctx: TranslationContext) : ConceptPass(ctx)
         }
 
         val option =
-            ConfigurationOptionSource(underlyingNode = field, group = group).also {
+            newConfigurationOptionSource(underlyingNode = field, concept = group).also {
                 it.name = field.name
             }
 
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/python/PythonStdLibConfigurationPass.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/python/PythonStdLibConfigurationPass.kt
index 6e2bfbf0a0e..7a4f8059325 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/python/PythonStdLibConfigurationPass.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/config/python/PythonStdLibConfigurationPass.kt
@@ -69,7 +69,7 @@ class PythonStdLibConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx)
      */
     private fun handleConstructExpression(expr: ConstructExpression): Configuration? {
         if (expr.name.toString() == "configparser.ConfigParser") {
-            val conf = Configuration(underlyingNode = expr)
+            val conf = newConfiguration(underlyingNode = expr)
             expr.prevDFG += conf
             return conf
         }
@@ -89,11 +89,13 @@ class PythonStdLibConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx)
                 call.base?.followDFGEdgesUntilHit(direction = Backward(GraphToFollow.DFG)) {
                     it is Configuration
                 }
-            paths?.fulfilled?.map {
-                val conf = it.last() as Configuration
-                val op = LoadConfiguration(call, conf, fileExpression = firstArgument)
-                op
-            }
+            paths
+                ?.fulfilled
+                ?.mapNotNull { it.lastOrNull() as? Configuration }
+                ?.toSet()
+                ?.forEach { conf ->
+                    newLoadConfiguration(call, concept = conf, fileExpression = firstArgument)
+                }
         }
 
         return null
@@ -147,13 +149,12 @@ class PythonStdLibConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx)
         var group = conf.groups.find { it.name.localName == name }
         if (group == null) {
             // If it does not exist, we create it and implicitly add a registration operation
-            group = ConfigurationGroup(sub, conf = conf).also { it.name = Name(name) }.implicit()
-            val op = RegisterConfigurationGroup(sub, group = group).implicit()
-            ops += op
+            group =
+                newConfigurationGroup(sub, concept = conf).also { it.name = Name(name) }.implicit()
+            newRegisterConfigurationGroup(sub, concept = group).implicit()
         }
 
-        val op = ReadConfigurationGroup(sub, group = group)
-        ops += op
+        newReadConfigurationGroup(sub, concept = group)
 
         // Add an incoming DFG from the option group
         sub.prevDFGEdges.add(group)
@@ -163,7 +164,7 @@ class PythonStdLibConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx)
 
     /**
      * Translates an option access (`config["group"]["option"]`) into a [ReadConfigurationOption]
-     * opertion.
+     * operation.
      */
     private fun handleOptionAccess(
         group: ConfigurationGroup,
@@ -177,7 +178,7 @@ class PythonStdLibConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx)
             warnWithFileLocation(
                 sub,
                 log,
-                "We could not evaluate the configuration group name to a string",
+                "We could not evaluate the configuration option name to a string",
             )
             return ops
         }
@@ -186,15 +187,13 @@ class PythonStdLibConfigurationPass(ctx: TranslationContext) : ConceptPass(ctx)
         if (option == null) {
             // If it does not exist, we create it and implicitly add a registration operation
             option =
-                ConfigurationOption(sub, group = group, key = sub)
+                newConfigurationOption(sub, key = sub, concept = group, value = null)
                     .also { it.name = group.name.fqn(name) }
                     .implicit()
-            val op = RegisterConfigurationOption(sub, option = option).implicit()
-            ops += op
+            newRegisterConfigurationOption(sub, concept = option, defaultValue = null).implicit()
         }
 
-        val op = ReadConfigurationOption(sub, option = option)
-        ops += op
+        newReadConfigurationOption(sub, concept = option)
 
         // Add an incoming DFG from the option
         sub.prevDFGEdges.add(option)
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/flows/cxx/CXXEntryPointsPass.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/flows/cxx/CXXEntryPointsPass.kt
index 6654ce75dfd..b35d25dfea7 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/flows/cxx/CXXEntryPointsPass.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/flows/cxx/CXXEntryPointsPass.kt
@@ -30,8 +30,8 @@ import de.fraunhofer.aisec.cpg.graph.Node
 import de.fraunhofer.aisec.cpg.graph.concepts.arch.POSIX
 import de.fraunhofer.aisec.cpg.graph.concepts.arch.Win32
 import de.fraunhofer.aisec.cpg.graph.concepts.flows.EntryPoint
-import de.fraunhofer.aisec.cpg.graph.concepts.flows.LibraryEntryPoint
-import de.fraunhofer.aisec.cpg.graph.concepts.flows.Main
+import de.fraunhofer.aisec.cpg.graph.concepts.flows.newLibraryEntryPoint
+import de.fraunhofer.aisec.cpg.graph.concepts.flows.newMain
 import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
 import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
 import de.fraunhofer.aisec.cpg.passes.concepts.ConceptPass
@@ -52,10 +52,10 @@ class CXXEntryPointsPass(ctx: TranslationContext) : ConceptPass(ctx) {
     ) {
         val entry =
             when (func.name.toString()) {
-                "main" -> Main(underlyingNode = func, os = tu.getConceptOrCreate<POSIX>())
-                "WinMain" -> Main(underlyingNode = func, os = tu.getConceptOrCreate<Win32>())
+                "main" -> newMain(underlyingNode = func, os = tu.getConceptOrCreate<POSIX>())
+                "WinMain" -> newMain(underlyingNode = func, os = tu.getConceptOrCreate<Win32>())
                 "DllMain" ->
-                    LibraryEntryPoint(underlyingNode = func, os = tu.getConceptOrCreate<Win32>())
+                    newLibraryEntryPoint(underlyingNode = func, os = tu.getConceptOrCreate<Win32>())
                 else -> return
             }
 
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/logging/python/PythonLoggingConceptPass.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/logging/python/PythonLoggingConceptPass.kt
index 5816b5c1092..018a0c4b6a5 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/logging/python/PythonLoggingConceptPass.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/logging/python/PythonLoggingConceptPass.kt
@@ -68,7 +68,7 @@ class PythonLoggingConceptPass(ctx: TranslationContext) : ComponentPass(ctx) {
      */
     private val loggers = mutableMapOf<String, Log>()
 
-    private val DEFAULT_LOGGER_NAME = ""
+    private val defaultLoggerName = ""
 
     /** The global `import logging` node. */
     private var loggingLogger: ImportDeclaration? = null
@@ -101,17 +101,13 @@ class PythonLoggingConceptPass(ctx: TranslationContext) : ComponentPass(ctx) {
      */
     private fun handleImport(importDeclaration: ImportDeclaration) {
         if (importDeclaration.import.toString() == "logging") {
-            val logger = loggers[DEFAULT_LOGGER_NAME]
-            if (logger == null) { // only add it once
-                val newNode = newLog(underlyingNode = importDeclaration, name = DEFAULT_LOGGER_NAME)
-                loggers += DEFAULT_LOGGER_NAME to newNode
-
-                // also add a [LogGet] node
-                newLogGet(underlyingNode = importDeclaration, logger = newNode)
-            } else {
-                // the logger is already present -> only add a [LogGet] node
-                newLogGet(underlyingNode = importDeclaration, logger = logger)
-            }
+            // Add the GetLog operation to the existing Log concept or generate a new one if there
+            // is nothing available yet.
+            val logger =
+                loggers.computeIfAbsent(defaultLoggerName) {
+                    newLog(underlyingNode = importDeclaration, name = defaultLoggerName)
+                }
+            newLogGet(underlyingNode = importDeclaration, concept = logger)
         }
     }
 
@@ -134,20 +130,16 @@ class PythonLoggingConceptPass(ctx: TranslationContext) : ComponentPass(ctx) {
                 when (loggerName) {
                     "",
                     "null" // Pythons `None` is translated to a Kotlin `null`
-                    -> DEFAULT_LOGGER_NAME
+                    -> defaultLoggerName
                     else -> loggerName
                 }
-            val logger = loggers[normalizedLoggerName]
-            if (logger == null) { // only add it once
-                val newNode = newLog(underlyingNode = callExpression, name = normalizedLoggerName)
-                loggers += normalizedLoggerName to newNode
-                newLogGet(underlyingNode = callExpression, logger = newNode)
-            } else {
-                // the logger is already present -> only add a [LogGet] node
-                newLogGet(underlyingNode = callExpression, logger = logger)
-            }
+            val logger =
+                loggers.computeIfAbsent(normalizedLoggerName) {
+                    newLog(underlyingNode = callExpression, name = normalizedLoggerName)
+                }
+            newLogGet(underlyingNode = callExpression, concept = logger)
         } else if (callee.name.toString().startsWith("logging.")) {
-            loggers[DEFAULT_LOGGER_NAME]?.let { logOpHelper(callExpression, it) }
+            loggers[defaultLoggerName]?.let { logOpHelper(callExpression, it) }
         } else {
             // might be a call like `logger.error`
             val logger = findLogger(callExpression)
@@ -220,7 +212,7 @@ class PythonLoggingConceptPass(ctx: TranslationContext) : ComponentPass(ctx) {
                 val lvl = logLevelStringToEnum(name)
                 newLogWrite(
                     underlyingNode = callExpression,
-                    logger = logger,
+                    concept = logger,
                     logArguments = callExpression.arguments,
                     level = lvl,
                 )
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/memory/cxx/CXXDynamicLoadingPass.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/memory/cxx/CXXDynamicLoadingPass.kt
index cb1c0a70368..5570f8df961 100644
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/memory/cxx/CXXDynamicLoadingPass.kt
+++ b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/passes/concepts/memory/cxx/CXXDynamicLoadingPass.kt
@@ -35,6 +35,8 @@ import de.fraunhofer.aisec.cpg.graph.concepts.flows.LibraryEntryPoint
 import de.fraunhofer.aisec.cpg.graph.concepts.memory.DynamicLoading
 import de.fraunhofer.aisec.cpg.graph.concepts.memory.LoadLibrary
 import de.fraunhofer.aisec.cpg.graph.concepts.memory.LoadSymbol
+import de.fraunhofer.aisec.cpg.graph.concepts.memory.newLoadLibrary
+import de.fraunhofer.aisec.cpg.graph.concepts.memory.newLoadSymbol
 import de.fraunhofer.aisec.cpg.graph.declarations.FunctionDeclaration
 import de.fraunhofer.aisec.cpg.graph.declarations.TranslationUnitDeclaration
 import de.fraunhofer.aisec.cpg.graph.declarations.ValueDeclaration
@@ -79,8 +81,6 @@ class CXXDynamicLoadingPass(ctx: TranslationContext) : ConceptPass(ctx) {
                 "dlsym" -> handleLoadFunction(call, concept)
                 else -> return
             }
-
-        concept.ops += ops
     }
 
     /**
@@ -107,33 +107,34 @@ class CXXDynamicLoadingPass(ctx: TranslationContext) : ConceptPass(ctx) {
 
         // We need to create one operation for each nextDFG (hopefully there is only one). This
         // helps us to determine the type of the operation.
-        return call.nextFullDFG.filterIsInstance<Expression>().map { assignee ->
-            val op =
-                if (assignee.type is FunctionPointerType) {
-                    candidates = candidates?.filterIsInstance<FunctionDeclaration>()
-                    LoadSymbol(
-                        underlyingNode = call,
-                        concept = concept,
-                        what = candidates?.singleOrNull(),
-                        loader = loadLibrary,
-                    )
-                } else {
-                    candidates = candidates?.filterIsInstance<VariableDeclaration>()
-                    LoadSymbol(
-                        underlyingNode = call,
-                        concept = concept,
-                        what = candidates?.singleOrNull(),
-                        loader = loadLibrary,
-                    )
-                }
+        call.nextFullDFG.filterIsInstance<Expression>().forEach { assignee ->
+            if (assignee.type is FunctionPointerType) {
+                candidates = candidates?.filterIsInstance<FunctionDeclaration>()
+                newLoadSymbol(
+                    underlyingNode = call,
+                    concept = concept,
+                    what = candidates?.singleOrNull(),
+                    loader = loadLibrary,
+                    os = loadLibrary?.os,
+                )
+            } else {
+                candidates = candidates?.filterIsInstance<VariableDeclaration>()
+                newLoadSymbol(
+                    underlyingNode = call,
+                    concept = concept,
+                    what = candidates?.singleOrNull(),
+                    loader = loadLibrary,
+                    os = loadLibrary?.os,
+                )
+            }
 
             // We can help the dynamic invoke resolver by adding a DFG path from the declaration to
             // the "return value" of dlsym
             candidates?.forEach {
                 call.prevDFGEdges.addContextSensitive(it, callingContext = CallingContextOut(call))
             }
-            op
         }
+        return listOf()
     }
 
     /**
@@ -164,16 +165,15 @@ class CXXDynamicLoadingPass(ctx: TranslationContext) : ConceptPass(ctx) {
                 ?: emptyList()
 
         // Create the op
-        val op =
-            LoadLibrary(
-                underlyingNode = call,
-                concept = concept,
-                what = component,
-                entryPoints = entryPoints,
-                os = os,
-            )
-
-        return listOf(op)
+        newLoadLibrary(
+            underlyingNode = call,
+            concept = concept,
+            what = component,
+            entryPoints = entryPoints,
+            os = os,
+        )
+
+        return listOf()
     }
 
     fun TranslationResult.findComponentForLibrary(libraryName: String): Component? {
diff --git a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/query/FlowConceptQueries.kt b/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/query/FlowConceptQueries.kt
deleted file mode 100644
index cf751b18800..00000000000
--- a/cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/query/FlowConceptQueries.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2025, 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.query
-
-import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend.Companion.log
-import de.fraunhofer.aisec.cpg.graph.Node
-import de.fraunhofer.aisec.cpg.graph.OverlayNode
-import de.fraunhofer.aisec.cpg.helpers.Util
-
-/**
- * A helper function to check for execution order from one [OverlayNode] to another. These nodes are
- * usually not directly connected via the EOG, thus we have to check whether the underlying node has
- * an execution path to another node which has the desired [endNode] as [Node.overlays].
- *
- * @param startNode The [OverlayNode] to start at. Search begins at the connected
- *   [OverlayNode.underlyingNode].
- * @param endNode The [OverlayNode] to look for. We check whether the node on the EOG path has
- *   [endNode] as one of its [Node.overlays].
- * @return True, if an execution path was found. False if no pass was found or an error occurred
- *   (check the log).
- */
-// TODO remove once #2124 is merged
-fun executionPathHelper(startNode: OverlayNode, endNode: OverlayNode): Boolean {
-    val startUnderlyingNode = startNode.underlyingNode
-    if (startUnderlyingNode == null) {
-        Util.errorWithFileLocation(
-            startNode,
-            log, // TODO which log should be used here?
-            "Expected to find a underlying node. Returning false.",
-        )
-        return false
-    }
-    return executionPath(startNode = startUnderlyingNode) { node ->
-            node.overlays.any { overlay -> overlay == endNode }
-        }
-        .value
-}
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 28d0ac998ed..bca05aa4909 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
@@ -28,6 +28,7 @@ package de.fraunhofer.aisec.cpg.graph
 import de.fraunhofer.aisec.cpg.frontends.NoLanguage
 import de.fraunhofer.aisec.cpg.graph.edges.overlay.OverlaySingleEdge
 import de.fraunhofer.aisec.cpg.graph.edges.unwrapping
+import java.util.Objects
 import org.neo4j.ogm.annotation.Relationship
 
 /**
@@ -46,4 +47,12 @@ abstract class OverlayNode() : Node() {
         OverlaySingleEdge(this, of = null, mirrorProperty = Node::overlayEdges, outgoing = false)
 
     var underlyingNode by unwrapping(OverlayNode::underlyingNodeEdge)
+
+    override fun equals(other: Any?): Boolean {
+        return other is OverlayNode &&
+            super.equals(other) &&
+            other.underlyingNode == this.underlyingNode
+    }
+
+    override fun hashCode() = Objects.hash(super.hashCode(), underlyingNode)
 }
diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSingletonList.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSingletonList.kt
index ea27676f8d5..e0ac280cd68 100644
--- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSingletonList.kt
+++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/collections/EdgeSingletonList.kt
@@ -183,7 +183,7 @@ open class EdgeSingletonList<
             return (if (outgoing) {
                 this@EdgeSingletonList.element?.end
             } else {
-                this@EdgeSingletonList.element?.start as NodeType
+                this@EdgeSingletonList.element?.start as NodeType?
             })
                 as NullableNodeType
         }


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 17 out of 17 changed files in this pull request and generated no comments.




state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T08:43:50Z
user: login: oxisto
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-12T08:48:14Z
user: login: KuechA
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T10:07:03Z
user: login: maximiliankaul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T17:15:24Z
user: login: KuechA
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T17:18:14Z
user: login: oxisto
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T17:42:41Z
user: login: KuechA
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T18:02:42Z
user: login: maximiliankaul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:40:19Z
user: login: maximiliankaul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:46:48Z
user: login: maximiliankaul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:49:30Z
user: login: maximiliankaul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:50:12Z
user: login: maximiliankaul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:50:36Z
user: login: maximiliankaul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:57:34Z
user: login: maximiliankaul
body: I reviewed the changes concerning everyone + the File and Log changes. LGTM. I haven't reviewed the other concepts.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T09:59:07Z
user: login: KuechA
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T10:04:09Z
user: login: KuechA
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T10:04:46Z
user: login: maximiliankaul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T15:24:38Z
user: login: maximiliankaul
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T15:35:24Z

Review comments:
diff_hunk: @@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
+        this.name =
+            Name("${ConceptClass::class.simpleName}[" + underlyingNode.name.toString() + "]")
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted int he EOG
+ * after the [underlyingNode] but it is not connected by the DFG.
+ */
+fun <OperationClass : Operation, ConceptClass : Concept> MetadataProvider.newOperation(
+    constructor: (underlyingNode: Node, concept: ConceptClass) -> (OperationClass),
+    underlyingNode: Node,
+    concept: ConceptClass,
+): OperationClass =
+    constructor(underlyingNode, concept).codeAndLocationFrom(underlyingNode).apply {
+        this.name = concept.name
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: oxisto
body: Two things:
1) Can we expose the name in the builder? I often set this to a specific name
2) Wouldn't it make more sense to default to the underlying node's name? Often this is a call expression and it would be nice to have the name of the call here. Opinions?
created_at: 2025-03-12T08:48:08Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
+        this.name =
+            Name("${ConceptClass::class.simpleName}[" + underlyingNode.name.toString() + "]")
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted int he EOG
+ * after the [underlyingNode] but it is not connected by the DFG.
+ */
+fun <OperationClass : Operation, ConceptClass : Concept> MetadataProvider.newOperation(
+    constructor: (underlyingNode: Node, concept: ConceptClass) -> (OperationClass),
+    underlyingNode: Node,
+    concept: ConceptClass,
+): OperationClass =
+    constructor(underlyingNode, concept).codeAndLocationFrom(underlyingNode).apply {
+        this.name = concept.name
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: KuechA
body: I would somewhat avoid 1 and it's always possible to set a custom name by calling `.apply`

I have no opinion on 2 for the `Concept`s and it should be easy to change. For the operations, I changed the name to something like "${concept.name}.operation".
created_at: 2025-03-12T10:07:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
+        this.name = Name("${ConceptClass::class.simpleName}", underlyingNode.name)
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted int he EOG
+ * after the [underlyingNode] but it is not connected by the DFG.
+ */
+inline fun <reified OperationClass : Operation, ConceptClass : Concept> MetadataProvider
+    .newOperation(
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: maximiliankaul
body: Wasn't this supposed to magically re-wire EOG and DFG edges? Or is this happening some other place for DFG?
created_at: 2025-03-12T17:15:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
+        this.name = Name("${ConceptClass::class.simpleName}", underlyingNode.name)
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted int he EOG
+ * after the [underlyingNode] but it is not connected by the DFG.
+ */
+inline fun <reified OperationClass : Operation, ConceptClass : Concept> MetadataProvider
+    .newOperation(
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: KuechA
body: It's writing the EOG edges with `insertNodeAfterwardInEOGPath`
created_at: 2025-03-12T17:18:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
+        this.name = Name("${ConceptClass::class.simpleName}", underlyingNode.name)
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted int he EOG
+ * after the [underlyingNode] but it is not connected by the DFG.
+ */
+inline fun <reified OperationClass : Operation, ConceptClass : Concept> MetadataProvider
+    .newOperation(
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: oxisto
body: The DFG is up to the user I guess since we can’t know which direction - if any we want to draw the edges
created_at: 2025-03-12T17:42:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
+        this.name = Name("${ConceptClass::class.simpleName}", underlyingNode.name)
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted int he EOG
+ * after the [underlyingNode] but it is not connected by the DFG.
+ */
+inline fun <reified OperationClass : Operation, ConceptClass : Concept> MetadataProvider
+    .newOperation(
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: KuechA
body: Correct. The DFG edges have to be added manually by the user since it depends on the `Operation`. Also, we do not add the EOG or DFG for the `Concept` nodes for now.
created_at: 2025-03-12T18:02:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
+        this.name = Name("${ConceptClass::class.simpleName}", underlyingNode.name)
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted int he EOG
+ * after the [underlyingNode] but it is not connected by the DFG.
+ */
+inline fun <reified OperationClass : Operation, ConceptClass : Concept> MetadataProvider
+    .newOperation(
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: maximiliankaul
body: I like this :+1: Resolving.
created_at: 2025-03-13T09:40:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,14 +36,15 @@ import de.fraunhofer.aisec.cpg.graph.OverlayNode
 abstract class Operation(
     underlyingNode: Node,
     /** The [Concept] this operation belongs to. */
-    concept: Concept,
+    open val concept: Concept,
 ) : OverlayNode() {
 
-    /** The [Concept] this operation belongs to. */
-    open val concept: Concept = concept.also { it.ops += this }
-
     init {
         this.underlyingNode = underlyingNode
         this::class.simpleName?.let { name = Name(it) }
     }
+
+    override fun equals(other: Any?): Boolean {
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Operation.kt
user: login: maximiliankaul
body: My Intellij complains, that you only implement equals and no hash code.
created_at: 2025-03-13T09:46:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: maximiliankaul
body: ```suggestion
    constructor(underlyingNode).apply {
      this.codeAndLocationFrom(underlyingNode)
```
I think this is a bit nicer to read.
created_at: 2025-03-13T09:49:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
+        this.name = Name("${ConceptClass::class.simpleName}", underlyingNode.name)
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted int he EOG
+ * after the [underlyingNode] but it is not connected by the DFG.
+ */
+inline fun <reified OperationClass : Operation, ConceptClass : Concept> MetadataProvider
+    .newOperation(
+    constructor: (underlyingNode: Node, concept: ConceptClass) -> (OperationClass),
+    underlyingNode: Node,
+    concept: ConceptClass,
+): OperationClass =
+    constructor(underlyingNode, concept).codeAndLocationFrom(underlyingNode).apply {
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: maximiliankaul
body: ```suggestion
    constructor(underlyingNode, concept).apply {
      this.codeAndLocationFrom(underlyingNode)
```
Nicer to read?!?
created_at: 2025-03-13T09:50:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025, 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.concepts
+
+import de.fraunhofer.aisec.cpg.graph.MetadataProvider
+import de.fraunhofer.aisec.cpg.graph.Name
+import de.fraunhofer.aisec.cpg.graph.Node
+import de.fraunhofer.aisec.cpg.graph.NodeBuilder
+import de.fraunhofer.aisec.cpg.graph.codeAndLocationFrom
+import de.fraunhofer.aisec.cpg.graph.edges.flows.insertNodeAfterwardInEOGPath
+
+/**
+ * This function creates a new [Concept] node based on [ConceptClass]. It is neither connected by
+ * the EOG nor the DFG.
+ */
+internal inline fun <reified ConceptClass : Concept> MetadataProvider.newConcept(
+    constructor: (underlyingNode: Node) -> (ConceptClass),
+    underlyingNode: Node,
+): ConceptClass =
+    constructor(underlyingNode).codeAndLocationFrom(underlyingNode).apply {
+        this.name = Name("${ConceptClass::class.simpleName}", underlyingNode.name)
+        NodeBuilder.log(this)
+    }
+
+/**
+ * This function creates a new [Operation] node based on [OperationClass]. It is inserted int he EOG
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/GenericConceptBuilder.kt
user: login: maximiliankaul
body: ```suggestion
 * This function creates a new [Operation] node based on [OperationClass]. It is inserted in the EOG
```
created_at: 2025-03-13T09:50:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,4 +46,10 @@ abstract class OverlayNode() : Node() {
         OverlaySingleEdge(this, of = null, mirrorProperty = Node::overlayEdges, outgoing = false)
 
     var underlyingNode by unwrapping(OverlayNode::underlyingNodeEdge)
+
+    override fun equals(other: Any?): Boolean {
path: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt
user: login: maximiliankaul
body: My Intellij complains that you implement equals but not hashCode
created_at: 2025-03-13T09:57:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -46,4 +46,10 @@ abstract class OverlayNode() : Node() {
         OverlaySingleEdge(this, of = null, mirrorProperty = Node::overlayEdges, outgoing = false)
 
     var underlyingNode by unwrapping(OverlayNode::underlyingNodeEdge)
+
+    override fun equals(other: Any?): Boolean {
path: cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/OverlayNode.kt
user: login: KuechA
body: Mine too but there's some issue with the edges represented as sets which compute the hashCode before initializing the node or so, so a meaningful implementation would cause a crash
created_at: 2025-03-13T10:04:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,14 +36,15 @@ import de.fraunhofer.aisec.cpg.graph.OverlayNode
 abstract class Operation(
     underlyingNode: Node,
     /** The [Concept] this operation belongs to. */
-    concept: Concept,
+    open val concept: Concept,
 ) : OverlayNode() {
 
-    /** The [Concept] this operation belongs to. */
-    open val concept: Concept = concept.also { it.ops += this }
-
     init {
         this.underlyingNode = underlyingNode
         this::class.simpleName?.let { name = Name(it) }
     }
+
+    override fun equals(other: Any?): Boolean {
path: cpg-concepts/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/concepts/Operation.kt
user: login: KuechA
body: Mine too but there's some issue with the edges represented as sets which compute the hashCode before initializing the node or so, so a meaningful implementation would cause a crash
created_at: 2025-03-13T10:04:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -103,17 +100,29 @@ class FileConceptTest : BaseTest() {
         val fileOpen = fileNodes.filterIsInstance<OpenFile>().singleOrNull()
         assertNotNull(fileOpen)
         assertTrue(
-            executionPathHelper(startNode = fileOpen, endNode = readFile),
+            executionPath(startNode = fileOpen, predicate = { it == readFile }).value,
             "Expected to find an execution path from open to read.",
         )
 
         val fileRead = fileNodes.filterIsInstance<ReadFile>().singleOrNull()
         assertNotNull(fileRead)
-        val closeFile = fileNodes.filterIsInstance<CloseFile>().singleOrNull()
-        assertNotNull(closeFile)
+        assertEquals(
+            2,
+            fileNodes.filterIsInstance<CloseFile>().size,
+            "We expect 2 x close (one for normally exiting `with` and one for the `catch` exit) operations.",
+        )
+        val executionPathReadToCloseMay =
+            executionPath(startNode = fileRead, type = May, predicate = { it is CloseFile })
+        assertTrue(
+            executionPathReadToCloseMay.value,
+            "Expected to find an execution path from read to close. But it's not mandatory.",
+        )
+
+        val executionPathReadToCloseMust =
+            executionPath(startNode = fileRead, type = Must, predicate = { it is CloseFile })
         assertTrue(
-            executionPathHelper(startNode = fileRead, endNode = closeFile),
-            "Expected to find an execution path from read to close.",
+            executionPathReadToCloseMust.value,
+            "Expected to find an execution path from read to close. But it's not mandatory.",
path: cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/file/FileConceptTest.kt
user: login: maximiliankaul
body: ```suggestion
            "Expected to find an execution path from read to close. It's mandatory.",
```
created_at: 2025-03-13T15:24:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -103,17 +100,29 @@ class FileConceptTest : BaseTest() {
         val fileOpen = fileNodes.filterIsInstance<OpenFile>().singleOrNull()
         assertNotNull(fileOpen)
         assertTrue(
-            executionPathHelper(startNode = fileOpen, endNode = readFile),
+            executionPath(startNode = fileOpen, predicate = { it == readFile }).value,
             "Expected to find an execution path from open to read.",
         )
 
         val fileRead = fileNodes.filterIsInstance<ReadFile>().singleOrNull()
         assertNotNull(fileRead)
-        val closeFile = fileNodes.filterIsInstance<CloseFile>().singleOrNull()
-        assertNotNull(closeFile)
+        assertEquals(
+            2,
+            fileNodes.filterIsInstance<CloseFile>().size,
+            "We expect 2 x close (one for normally exiting `with` and one for the `catch` exit) operations.",
+        )
+        val executionPathReadToCloseMay =
path: cpg-concepts/src/integrationTest/kotlin/de/fraunhofer/aisec/cpg/concepts/file/FileConceptTest.kt
user: login: maximiliankaul
body: I don't understand testing `May` and `Must`. Isn't the first test pointless?
created_at: 2025-03-13T15:35:20Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-03-11T16:13:57Z
body: ## [Codecov](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) Report
Attention: Patch coverage is `75.09025%` with `69 lines` in your changes missing coverage. Please review.
> Project coverage is 78.28%. Comparing base [(`cdd54b7`)](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/commit/cdd54b7ca12d3314338325be43e43d0b1edc7eb8?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) to head [(`2f83b99`)](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/commit/2f83b99e905e0f72c4e6787fe886ec1b32999571?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC).
> Report is 1 commits behind head on main.

:white_check_mark: All tests successful. No failed tests found.

| [Files with missing lines](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) | Patch % | Lines |
|---|---|---|
| [...isec/cpg/graph/concepts/http/HttpConceptBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Fhttp%2FHttpConceptBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9odHRwL0h0dHBDb25jZXB0QnVpbGRlci5rdA==) | 0.00% | [26 Missing :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| [...h/concepts/diskEncryption/DiskEncryptionBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2FdiskEncryption%2FDiskEncryptionBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9kaXNrRW5jcnlwdGlvbi9EaXNrRW5jcnlwdGlvbkJ1aWxkZXIua3Q=) | 28.57% | [10 Missing :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| [...c/cpg/graph/concepts/auth/AuthenticationBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Fauth%2FAuthenticationBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9hdXRoL0F1dGhlbnRpY2F0aW9uQnVpbGRlci5rdA==) | 0.00% | [8 Missing :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| [...asses/concepts/memory/cxx/CXXDynamicLoadingPass.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fpasses%2Fconcepts%2Fmemory%2Fcxx%2FCXXDynamicLoadingPass.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9wYXNzZXMvY29uY2VwdHMvbWVtb3J5L2N4eC9DWFhEeW5hbWljTG9hZGluZ1Bhc3Mua3Q=) | 75.00% | [0 Missing and 6 partials :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| [...sec/cpg/graph/concepts/arch/ArchitectureBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Farch%2FArchitectureBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9hcmNoL0FyY2hpdGVjdHVyZUJ1aWxkZXIua3Q=) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| [...pts/config/python/PythonStdLibConfigurationPass.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fpasses%2Fconcepts%2Fconfig%2Fpython%2FPythonStdLibConfigurationPass.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9wYXNzZXMvY29uY2VwdHMvY29uZmlnL3B5dGhvbi9QeXRob25TdGRMaWJDb25maWd1cmF0aW9uUGFzcy5rdA==) | 75.00% | [1 Missing and 3 partials :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| [.../cpg/graph/concepts/memory/MemoryConceptBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Fmemory%2FMemoryConceptBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9tZW1vcnkvTWVtb3J5Q29uY2VwdEJ1aWxkZXIua3Q=) | 86.36% | [3 Missing :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| [...hofer/aisec/cpg/graph/concepts/file/FileBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Ffile%2FFileBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9maWxlL0ZpbGVCdWlsZGVyLmt0) | 91.66% | [2 Missing :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| [...ec/cpg/passes/concepts/config/ProvideConfigPass.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fpasses%2Fconcepts%2Fconfig%2FProvideConfigPass.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9wYXNzZXMvY29uY2VwdHMvY29uZmlnL1Byb3ZpZGVDb25maWdQYXNzLmt0) | 88.88% | [0 Missing and 2 partials :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| [.../aisec/cpg/graph/concepts/GenericConceptBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2FGenericConceptBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9HZW5lcmljQ29uY2VwdEJ1aWxkZXIua3Q=) | 93.33% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) |
| ... and [3 more](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) | |

<details><summary>Additional details and impacted files</summary>


| [Files with missing lines](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) | Coverage Δ | |
|---|---|---|
| [...r/aisec/cpg/graph/concepts/config/Configuration.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Fconfig%2FConfiguration.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9jb25maWcvQ29uZmlndXJhdGlvbi5rdA==) | `100.00% <100.00%> (ø)` | |
| [.../cpg/graph/concepts/config/ConfigurationBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Fconfig%2FConfigurationBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9jb25maWcvQ29uZmlndXJhdGlvbkJ1aWxkZXIua3Q=) | `100.00% <100.00%> (ø)` | |
| [...isec/cpg/graph/concepts/flows/EntryPointBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Fflows%2FEntryPointBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9mbG93cy9FbnRyeVBvaW50QnVpbGRlci5rdA==) | `100.00% <100.00%> (ø)` | |
| [...fraunhofer/aisec/cpg/graph/concepts/logging/Log.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Flogging%2FLog.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9sb2dnaW5nL0xvZy5rdA==) | `100.00% <100.00%> (ø)` | |
| [...aisec/cpg/graph/concepts/logging/LoggingBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Flogging%2FLoggingBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9sb2dnaW5nL0xvZ2dpbmdCdWlsZGVyLmt0) | `100.00% <100.00%> (ø)` | |
| [.../aisec/cpg/graph/concepts/memory/DynamicLoading.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2Fmemory%2FDynamicLoading.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9tZW1vcnkvRHluYW1pY0xvYWRpbmcua3Q=) | `80.00% <ø> (+3.80%)` | :arrow_up: |
| [...cepts/config/ini/IniFileConfigurationSourcePass.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fpasses%2Fconcepts%2Fconfig%2Fini%2FIniFileConfigurationSourcePass.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9wYXNzZXMvY29uY2VwdHMvY29uZmlnL2luaS9JbmlGaWxlQ29uZmlndXJhdGlvblNvdXJjZVBhc3Mua3Q=) | `62.16% <100.00%> (ø)` | |
| [...pg/passes/concepts/flows/cxx/CXXEntryPointsPass.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fpasses%2Fconcepts%2Fflows%2Fcxx%2FCXXEntryPointsPass.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9wYXNzZXMvY29uY2VwdHMvZmxvd3MvY3h4L0NYWEVudHJ5UG9pbnRzUGFzcy5rdA==) | `77.77% <100.00%> (ø)` | |
| [...c/cpg/graph/edges/collections/EdgeSingletonList.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-core%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fedges%2Fcollections%2FEdgeSingletonList.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvcmUvc3JjL21haW4va290bGluL2RlL2ZyYXVuaG9mZXIvYWlzZWMvY3BnL2dyYXBoL2VkZ2VzL2NvbGxlY3Rpb25zL0VkZ2VTaW5nbGV0b25MaXN0Lmt0) | `59.37% <100.00%> (+1.56%)` | :arrow_up: |
| [.../aisec/cpg/graph/concepts/GenericConceptBuilder.kt](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree&filepath=cpg-concepts%2Fsrc%2Fmain%2Fkotlin%2Fde%2Ffraunhofer%2Faisec%2Fcpg%2Fgraph%2Fconcepts%2FGenericConceptBuilder.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC#diff-Y3BnLWNvbmNlcHRzL3NyYy9tYWluL2tvdGxpbi9kZS9mcmF1bmhvZmVyL2Fpc2VjL2NwZy9ncmFwaC9jb25jZXB0cy9HZW5lcmljQ29uY2VwdEJ1aWxkZXIua3Q=) | `93.33% <93.33%> (ø)` | |
| ... and [12 more](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC) | |

... and [8 files with indirect coverage changes](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC)

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/Fraunhofer-AISEC/cpg/pull/2124?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fraunhofer-AISEC).

<details><summary>🚀 New features to boost your workflow: </summary>

- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: maximiliankaul
created_at: 2025-03-12T14:53:28Z
body: Remove `executionPathHelper` in #2100 once this pr is merged.
author_association: CONTRIBUTOR
user: login: KuechA
created_at: 2025-03-13T10:09:28Z
body: @oxisto Can you please check if we can/should delete some constructor parameters in the `Configuration` concepts?
author_association: CONTRIBUTOR