From 09be5e1bf7468126d97a624da7bbe8989e5a5e66 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Wed, 6 Nov 2024 21:07:46 +0100 Subject: [PATCH 1/7] Optimized region utils (#1822) * Optimized region utils It seems that region utils (whatever they do) checked for a potential new line character in every call of `codeAndLocationFromChildren`. But the new line char should stay constant within the file, so we are using the `lineSeperator` property (in the python frontend) instead. * Rename to lineBreakSequence --------- Co-authored-by: Maximilian Kaul --- .../fraunhofer/aisec/cpg/graph/NodeBuilder.kt | 13 +++-- .../aisec/cpg/helpers/RegionUtils.kt | 47 +++++++------------ .../cpg/frontends/python/ExpressionHandler.kt | 2 +- .../python/PythonLanguageFrontend.kt | 4 +- .../cpg/frontends/python/StatementHandler.kt | 2 +- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt index 6e323eb7c5..ac060fa20a 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/NodeBuilder.kt @@ -32,7 +32,6 @@ import de.fraunhofer.aisec.cpg.graph.NodeBuilder.LOGGER import de.fraunhofer.aisec.cpg.graph.NodeBuilder.log import de.fraunhofer.aisec.cpg.graph.scopes.Scope import de.fraunhofer.aisec.cpg.graph.statements.expressions.* -import de.fraunhofer.aisec.cpg.graph.types.* import de.fraunhofer.aisec.cpg.helpers.getCodeOfSubregion import de.fraunhofer.aisec.cpg.passes.inference.IsImplicitProvider import de.fraunhofer.aisec.cpg.passes.inference.IsInferredProvider @@ -299,10 +298,16 @@ fun T.codeAndLocationFromOtherRawNode(rawNode: AstNode?): T * code is extracted from the parent node to catch separators and auxiliary syntactic elements that * are between the child nodes. * - * @param parentNode Used to extract the code for this node + * @param parentNode Used to extract the code for this node. + * @param newLineType The char(s) used to describe a new line, usually either "\n" or "\r\n". This + * is needed because the location block spanning the children usually comprises more than one + * line. */ context(CodeAndLocationProvider) -fun T.codeAndLocationFromChildren(parentNode: AstNode): T { +fun T.codeAndLocationFromChildren( + parentNode: AstNode, + lineBreakSequence: CharSequence = "\n" +): T { var first: Node? = null var last: Node? = null @@ -358,7 +363,7 @@ fun T.codeAndLocationFromChildren(parentNode: AstNode): T { val parentRegion = this@CodeAndLocationProvider.locationOf(parentNode)?.region if (parentCode != null && parentRegion != null) { // If the parent has code and region the new region is used to extract the code - this.code = getCodeOfSubregion(parentCode, parentRegion, newRegion) + this.code = getCodeOfSubregion(parentCode, parentRegion, newRegion, lineBreakSequence) } } diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/RegionUtils.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/RegionUtils.kt index e43c3d35c9..969225e69b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/RegionUtils.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/RegionUtils.kt @@ -25,50 +25,39 @@ */ package de.fraunhofer.aisec.cpg.helpers -import de.fraunhofer.aisec.cpg.frontends.LanguageFrontend import de.fraunhofer.aisec.cpg.sarif.Region import kotlin.math.min import org.apache.commons.lang3.StringUtils /** - * To prevent issues with different newline types and formatting. - * - * @param multilineCode The newline type is extracted from the code assuming it contains newlines - * @return the String of the newline or \n as default + * Returns the part of the [code] described by [subRegion], embedded in [nodeRegion]. [newLineType] + * can be used to specify the type of new-line char(s) used on the platform. */ -fun getNewLineType(multilineCode: String, region: Region? = null): String { - var code = multilineCode - region?.let { - if (it.startLine != it.endLine) { - code = code.substring(0, code.length - it.endColumn + 1) - } - } - - val nls = listOf("\n\r", "\r\n", "\n") - for (nl in nls) { - if (code.endsWith(nl)) { - return nl - } - } - LanguageFrontend.log.debug("Could not determine newline type. Assuming \\n.") - return "\n" -} - -fun getCodeOfSubregion(code: String, nodeRegion: Region, subRegion: Region): String { - val nlType = getNewLineType(code, nodeRegion) +fun getCodeOfSubregion( + code: String, + nodeRegion: Region, + subRegion: Region, + lineBreakSequence: CharSequence = "\n" +): String { val start = if (subRegion.startLine == nodeRegion.startLine) { subRegion.startColumn - nodeRegion.startColumn } else { - (StringUtils.ordinalIndexOf(code, nlType, subRegion.startLine - nodeRegion.startLine) + - subRegion.startColumn) + (StringUtils.ordinalIndexOf( + code, + lineBreakSequence, + subRegion.startLine - nodeRegion.startLine + ) + subRegion.startColumn) } var end = if (subRegion.endLine == nodeRegion.startLine) { subRegion.endColumn - nodeRegion.startColumn } else { - (StringUtils.ordinalIndexOf(code, nlType, subRegion.endLine - nodeRegion.startLine) + - subRegion.endColumn) + (StringUtils.ordinalIndexOf( + code, + lineBreakSequence, + subRegion.endLine - nodeRegion.startLine + ) + subRegion.endColumn) } // Unfortunately, we sometimes have issues with (non)-Unicode characters in code, where the diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt index 20dd22abb3..ac844775a2 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/ExpressionHandler.kt @@ -364,7 +364,7 @@ class ExpressionHandler(frontend: PythonLanguageFrontend) : node.keys[i]?.let { handle(it) } ?: newProblemExpression("missing key"), value = handle(node.values[i]), ) - .codeAndLocationFromChildren(node) + .codeAndLocationFromChildren(node, frontend.lineSeparator) } val ile = newInitializerListExpression(rawNode = node) ile.type = frontend.objectType("dict") diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt index dd56616062..98adf9ad7c 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/PythonLanguageFrontend.kt @@ -51,7 +51,7 @@ import kotlin.math.min @RegisterExtraPass(PythonAddDeclarationsPass::class) class PythonLanguageFrontend(language: Language, ctx: TranslationContext) : LanguageFrontend(language, ctx) { - private val lineSeparator = '\n' // TODO + val lineSeparator = "\n" // TODO private val tokenTypeIndex = 0 private val jep = JepSingleton // configure Jep @@ -191,7 +191,7 @@ class PythonLanguageFrontend(language: Language, ctx: Tr lines = removeExtraAtEnd(location, lines) lines = fixStartColumn(location, lines) - lines.joinToString(separator = lineSeparator.toString()) + lines.joinToString(separator = lineSeparator) } else { null } diff --git a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt index a8ea976d29..cdb66e7103 100644 --- a/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt +++ b/cpg-language-python/src/main/kotlin/de/fraunhofer/aisec/cpg/frontends/python/StatementHandler.kt @@ -1056,7 +1056,7 @@ class StatementHandler(frontend: PythonLanguageFrontend) : // and only Python.AST.BaseStmt nodes would be accepted. This would cause issues with // other nodes that are not "statements", but also handled as part of this handler, // e.g., the Python.AST.ExceptHandler. - with(frontend) { result.codeAndLocationFromChildren(ast) } + with(frontend) { result.codeAndLocationFromChildren(ast, frontend.lineSeparator) } } return result From d025c207695034ad9f40f916f7b1fd3d15f45027 Mon Sep 17 00:00:00 2001 From: Christian Banse Date: Thu, 7 Nov 2024 17:23:36 +0100 Subject: [PATCH 2/7] Persist Dataflow's granularity in Neo4J (#1825) Fixes #1629 Co-authored-by: KuechA <31155350+KuechA@users.noreply.github.com> --- .../aisec/cpg/graph/edges/flows/Dataflow.kt | 6 +- .../neo4j/DataflowGranularityConverter.kt | 61 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/DataflowGranularityConverter.kt diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Dataflow.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Dataflow.kt index 206e554d31..900eedc09b 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Dataflow.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/edges/flows/Dataflow.kt @@ -33,8 +33,10 @@ import de.fraunhofer.aisec.cpg.graph.edges.collections.EdgeSet import de.fraunhofer.aisec.cpg.graph.edges.collections.MirroredEdgeCollection import de.fraunhofer.aisec.cpg.graph.statements.expressions.* import de.fraunhofer.aisec.cpg.graph.types.HasType +import de.fraunhofer.aisec.cpg.helpers.neo4j.DataflowGranularityConverter import kotlin.reflect.KProperty import org.neo4j.ogm.annotation.* +import org.neo4j.ogm.annotation.typeconversion.Convert /** * The granularity of the data-flow, e.g., whether the flow contains the whole object, or just a @@ -87,7 +89,9 @@ open class Dataflow( start: Node, end: Node, /** The granularity of this dataflow. */ - @Transient @JsonIgnore var granularity: Granularity = default() + @Convert(DataflowGranularityConverter::class) + @JsonIgnore + var granularity: Granularity = default() ) : Edge(start, end) { override val label: String = "DFG" diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/DataflowGranularityConverter.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/DataflowGranularityConverter.kt new file mode 100644 index 0000000000..3db0495b7e --- /dev/null +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/neo4j/DataflowGranularityConverter.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.helpers.neo4j + +import de.fraunhofer.aisec.cpg.graph.edges.flows.Granularity +import de.fraunhofer.aisec.cpg.graph.edges.flows.PartialDataflowGranularity + +/** This converter converts a [Granularity] into a string-based representation in Neo4J. */ +class DataflowGranularityConverter : CpgCompositeConverter { + companion object { + const val FIELD_GRANULARITY = "granularity" + const val FIELD_PARTIAL_TARGET = "partialTarget" + } + + override fun toGraphProperties(value: Granularity): MutableMap { + val map = mutableMapOf() + + val type = value::class.simpleName?.substringBefore("DataflowGranularity")?.uppercase() + if (type != null) { + // The type of granularity + map[FIELD_GRANULARITY] = type + } + + // Only for partial + if (value is PartialDataflowGranularity) { + map[FIELD_PARTIAL_TARGET] = value.partialTarget?.name.toString() + } + + return map + } + + override val graphSchema: List> + get() = listOf(Pair("String", FIELD_GRANULARITY), Pair("String", FIELD_PARTIAL_TARGET)) + + override fun toEntityAttribute(value: MutableMap): Granularity { + throw UnsupportedOperationException() + } +} From a36ebaa2e9c2d97cace391bff9cbc013a860c2d7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 10:42:53 +0000 Subject: [PATCH 3/7] Update dependency @types/node to v22.9.0 (#1826) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cpg-language-typescript/src/main/nodejs/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index 3124d88a24..60dd882837 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -383,9 +383,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.8.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.2.tgz", - "integrity": "sha512-NzaRNFV+FZkvK/KLCsNdTvID0SThyrs5SHB6tsD/lajr22FGC73N2QeDPM2wHtVde8mgcXuSsHQkH5cX1pbPLw==", + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", "license": "MIT", "dependencies": { "undici-types": "~6.19.8" From fc3b9df8a7cac073f904600096a9c12bf77847a4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 10 Nov 2024 10:27:48 +0100 Subject: [PATCH 4/7] Update dependency rollup to v4.25.0 (#1829) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/main/nodejs/package-lock.json | 164 +++++++++++------- 1 file changed, 97 insertions(+), 67 deletions(-) diff --git a/cpg-language-typescript/src/main/nodejs/package-lock.json b/cpg-language-typescript/src/main/nodejs/package-lock.json index 60dd882837..c3e47c71d1 100644 --- a/cpg-language-typescript/src/main/nodejs/package-lock.json +++ b/cpg-language-typescript/src/main/nodejs/package-lock.json @@ -152,9 +152,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", - "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz", + "integrity": "sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==", "cpu": [ "arm" ], @@ -166,9 +166,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", - "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz", + "integrity": "sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==", "cpu": [ "arm64" ], @@ -180,9 +180,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", - "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz", + "integrity": "sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==", "cpu": [ "arm64" ], @@ -194,9 +194,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", - "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz", + "integrity": "sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==", "cpu": [ "x64" ], @@ -207,10 +207,38 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz", + "integrity": "sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz", + "integrity": "sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", - "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz", + "integrity": "sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==", "cpu": [ "arm" ], @@ -222,9 +250,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", - "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz", + "integrity": "sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==", "cpu": [ "arm" ], @@ -236,9 +264,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", - "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz", + "integrity": "sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==", "cpu": [ "arm64" ], @@ -250,9 +278,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", - "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz", + "integrity": "sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==", "cpu": [ "arm64" ], @@ -264,9 +292,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", - "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz", + "integrity": "sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==", "cpu": [ "ppc64" ], @@ -278,9 +306,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", - "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz", + "integrity": "sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==", "cpu": [ "riscv64" ], @@ -292,9 +320,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", - "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz", + "integrity": "sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==", "cpu": [ "s390x" ], @@ -306,9 +334,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", - "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz", + "integrity": "sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==", "cpu": [ "x64" ], @@ -320,9 +348,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", - "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz", + "integrity": "sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==", "cpu": [ "x64" ], @@ -334,9 +362,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", - "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz", + "integrity": "sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==", "cpu": [ "arm64" ], @@ -348,9 +376,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", - "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz", + "integrity": "sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==", "cpu": [ "ia32" ], @@ -362,9 +390,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", - "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz", + "integrity": "sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==", "cpu": [ "x64" ], @@ -546,9 +574,9 @@ } }, "node_modules/rollup": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", - "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.25.0.tgz", + "integrity": "sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==", "dev": true, "license": "MIT", "dependencies": { @@ -562,22 +590,24 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.24.0", - "@rollup/rollup-android-arm64": "4.24.0", - "@rollup/rollup-darwin-arm64": "4.24.0", - "@rollup/rollup-darwin-x64": "4.24.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", - "@rollup/rollup-linux-arm-musleabihf": "4.24.0", - "@rollup/rollup-linux-arm64-gnu": "4.24.0", - "@rollup/rollup-linux-arm64-musl": "4.24.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", - "@rollup/rollup-linux-riscv64-gnu": "4.24.0", - "@rollup/rollup-linux-s390x-gnu": "4.24.0", - "@rollup/rollup-linux-x64-gnu": "4.24.0", - "@rollup/rollup-linux-x64-musl": "4.24.0", - "@rollup/rollup-win32-arm64-msvc": "4.24.0", - "@rollup/rollup-win32-ia32-msvc": "4.24.0", - "@rollup/rollup-win32-x64-msvc": "4.24.0", + "@rollup/rollup-android-arm-eabi": "4.25.0", + "@rollup/rollup-android-arm64": "4.25.0", + "@rollup/rollup-darwin-arm64": "4.25.0", + "@rollup/rollup-darwin-x64": "4.25.0", + "@rollup/rollup-freebsd-arm64": "4.25.0", + "@rollup/rollup-freebsd-x64": "4.25.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.25.0", + "@rollup/rollup-linux-arm-musleabihf": "4.25.0", + "@rollup/rollup-linux-arm64-gnu": "4.25.0", + "@rollup/rollup-linux-arm64-musl": "4.25.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.25.0", + "@rollup/rollup-linux-riscv64-gnu": "4.25.0", + "@rollup/rollup-linux-s390x-gnu": "4.25.0", + "@rollup/rollup-linux-x64-gnu": "4.25.0", + "@rollup/rollup-linux-x64-musl": "4.25.0", + "@rollup/rollup-win32-arm64-msvc": "4.25.0", + "@rollup/rollup-win32-ia32-msvc": "4.25.0", + "@rollup/rollup-win32-x64-msvc": "4.25.0", "fsevents": "~2.3.2" } }, From 451070cdcb8495abca7ef132273e11b2d5112188 Mon Sep 17 00:00:00 2001 From: KuechA <31155350+KuechA@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:01:55 +0100 Subject: [PATCH 5/7] Rework extensions to follow subgraphs (#1809) * Add next/prev CDG following extensions * Add next/prev CDG following extensions * Refactor: Eliminate duplicate code * Keyword args --- .../fraunhofer/aisec/cpg/graph/Extensions.kt | 261 ++++++++++-------- 1 file changed, 143 insertions(+), 118 deletions(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt index 0c9eb26b4b..a5400e768e 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/graph/Extensions.kt @@ -200,50 +200,115 @@ class FulfilledAndFailedPaths(val fulfilled: List>, val failed: List< * but not mandatory**. If the list "failed" is empty, the data flow is mandatory. */ fun Node.followPrevFullDFGEdgesUntilHit(predicate: (Node) -> Boolean): FulfilledAndFailedPaths { - val fulfilledPaths = mutableListOf>() - val failedPaths = mutableListOf>() - val worklist = mutableListOf>() - worklist.add(listOf(this)) + return followXUntilHit( + x = { currentNode -> currentNode.prevFullDFG }, + collectFailedPaths = true, + findAllPossiblePaths = true, + predicate = predicate + ) +} - while (worklist.isNotEmpty()) { - val currentPath = worklist.removeFirst() - if (currentPath.last().prevFullDFG.isEmpty()) { - // No further nodes in the path and the path criteria are not satisfied. - failedPaths.add(currentPath) - continue +fun Node.collectAllPrevCDGPaths(interproceduralAnalysis: Boolean): List> { + // We make everything fail to reach the end of the CDG. Then, we use the stuff collected in the + // failed paths (everything) + return this.followPrevCDGUntilHit( + collectFailedPaths = true, + findAllPossiblePaths = true, + interproceduralAnalysis = interproceduralAnalysis + ) { + false } + .failed +} - for (prev in currentPath.last().prevFullDFG) { - // Copy the path for each outgoing DFG edge and add the prev node - val nextPath = mutableListOf() - nextPath.addAll(currentPath) - nextPath.add(prev) - - if (predicate(prev)) { - fulfilledPaths.add(nextPath) - continue // Don't add this path anymore. The requirement is satisfied. - } - // The prev node is new in the current path (i.e., there's no loop), so we add the path - // with the next step to the worklist. - if (!currentPath.contains(prev)) { - worklist.add(nextPath) - } +fun Node.collectAllNextCDGPaths(interproceduralAnalysis: Boolean): List> { + // We make everything fail to reach the end of the CDG. Then, we use the stuff collected in the + // failed paths (everything) + return this.followNextCDGUntilHit( + collectFailedPaths = true, + findAllPossiblePaths = true, + interproceduralAnalysis = interproceduralAnalysis + ) { + false } - } - - return FulfilledAndFailedPaths(fulfilledPaths, failedPaths) + .failed } /** * Returns an instance of [FulfilledAndFailedPaths] where [FulfilledAndFailedPaths.fulfilled] - * contains all possible shortest data flow paths (with [FullDataflowGranularity]) between the - * starting node [this] and the end node fulfilling [predicate]. The paths are represented as lists - * of nodes. Paths which do not end at such a node are included in [FulfilledAndFailedPaths.failed]. + * contains all possible shortest data flow paths (with [ControlDependence]) between the starting + * node [this] and the end node fulfilling [predicate]. The paths are represented as lists of nodes. + * Paths which do not end at such a node are included in [FulfilledAndFailedPaths.failed]. * * Hence, if "fulfilled" is a non-empty list, a data flow from [this] to such a node is **possible * but not mandatory**. If the list "failed" is empty, the data flow is mandatory. */ -fun Node.followNextFullDFGEdgesUntilHit( +fun Node.followNextCDGUntilHit( + collectFailedPaths: Boolean = true, + findAllPossiblePaths: Boolean = true, + interproceduralAnalysis: Boolean = false, + predicate: (Node) -> Boolean +): FulfilledAndFailedPaths { + return followXUntilHit( + x = { currentNode -> + val nextNodes = currentNode.nextCDG.toMutableList() + if (interproceduralAnalysis) { + nextNodes.addAll((currentNode as? CallExpression)?.calls ?: listOf()) + } + nextNodes + }, + collectFailedPaths = collectFailedPaths, + findAllPossiblePaths = findAllPossiblePaths, + predicate = predicate + ) +} + +/** + * Returns an instance of [FulfilledAndFailedPaths] where [FulfilledAndFailedPaths.fulfilled] + * contains all possible shortest data flow paths (with [ControlDependence]) between the starting + * node [this] and the end node fulfilling [predicate] (backwards analysis). The paths are + * represented as lists of nodes. Paths which do not end at such a node are included in + * [FulfilledAndFailedPaths.failed]. + * + * Hence, if "fulfilled" is a non-empty list, a CDG path from [this] to such a node is **possible + * but not mandatory**. If the list "failed" is empty, the data flow is mandatory. + */ +fun Node.followPrevCDGUntilHit( + collectFailedPaths: Boolean = true, + findAllPossiblePaths: Boolean = true, + interproceduralAnalysis: Boolean = false, + predicate: (Node) -> Boolean +): FulfilledAndFailedPaths { + return followXUntilHit( + x = { currentNode -> + val nextNodes = currentNode.prevCDG.toMutableList() + if (interproceduralAnalysis) { + nextNodes.addAll( + (currentNode as? FunctionDeclaration)?.usages?.mapNotNull { + it.astParent as? CallExpression + } ?: listOf() + ) + } + nextNodes + }, + collectFailedPaths = collectFailedPaths, + findAllPossiblePaths = findAllPossiblePaths, + predicate = predicate + ) +} + +/** + * Returns an instance of [FulfilledAndFailedPaths] where [FulfilledAndFailedPaths.fulfilled] + * contains all possible shortest data flow paths (with [x] specifying how to fetch more nodes) + * between the starting node [this] and the end node fulfilling [predicate] (backwards analysis). + * The paths are represented as lists of nodes. Paths which do not end at such a node are included + * in [FulfilledAndFailedPaths.failed]. + * + * Hence, if "fulfilled" is a non-empty list, a path from [this] to such a node is **possible but + * not mandatory**. If the list "failed" is empty, the path is mandatory. + */ +fun Node.followXUntilHit( + x: (Node) -> List, collectFailedPaths: Boolean = true, findAllPossiblePaths: Boolean = true, predicate: (Node) -> Boolean @@ -264,15 +329,15 @@ fun Node.followNextFullDFGEdgesUntilHit( worklist.remove(currentPath) val currentNode = currentPath.last() alreadySeenNodes.add(currentNode) - // The last node of the path is where we continue. We get all of its outgoing DFG edges and + // The last node of the path is where we continue. We get all of its outgoing CDG edges and // follow them - if (currentNode.nextFullDFG.isEmpty()) { - // No further nodes in the path and the path criteria are not satisfied. - if (collectFailedPaths) failedPaths.add(currentPath) - } + var nextNodes = x(currentNode) + + // No further nodes in the path and the path criteria are not satisfied. + if (nextNodes.isEmpty() && collectFailedPaths) failedPaths.add(currentPath) - for (next in currentNode.nextFullDFG) { - // Copy the path for each outgoing DFG edge and add the next node + for (next in nextNodes) { + // Copy the path for each outgoing CDG edge and add the next node val nextPath = currentPath.toMutableList() nextPath.add(next) if (predicate(next)) { @@ -296,6 +361,28 @@ fun Node.followNextFullDFGEdgesUntilHit( return FulfilledAndFailedPaths(fulfilledPaths, failedPaths) } +/** + * Returns an instance of [FulfilledAndFailedPaths] where [FulfilledAndFailedPaths.fulfilled] + * contains all possible shortest data flow paths (with [FullDataflowGranularity]) between the + * starting node [this] and the end node fulfilling [predicate]. The paths are represented as lists + * of nodes. Paths which do not end at such a node are included in [FulfilledAndFailedPaths.failed]. + * + * Hence, if "fulfilled" is a non-empty list, a data flow from [this] to such a node is **possible + * but not mandatory**. If the list "failed" is empty, the data flow is mandatory. + */ +fun Node.followNextFullDFGEdgesUntilHit( + collectFailedPaths: Boolean = true, + findAllPossiblePaths: Boolean = true, + predicate: (Node) -> Boolean +): FulfilledAndFailedPaths { + return followXUntilHit( + x = { currentNode -> currentNode.nextFullDFG }, + collectFailedPaths = collectFailedPaths, + findAllPossiblePaths = findAllPossiblePaths, + predicate = predicate + ) +} + /** * Returns an instance of [FulfilledAndFailedPaths] where [FulfilledAndFailedPaths.fulfilled] * contains all possible shortest evaluation paths between the starting node [this] and the end node @@ -307,45 +394,14 @@ fun Node.followNextFullDFGEdgesUntilHit( * such a statement is always executed. */ fun Node.followNextEOGEdgesUntilHit(predicate: (Node) -> Boolean): FulfilledAndFailedPaths { - // Looks complicated but at least it's not recursive... - // result: List of paths (between from and to) - val fulfilledPaths = mutableListOf>() - // failedPaths: All the paths which do not satisfy "predicate" - val failedPaths = mutableListOf>() - // The list of paths where we're not done yet. - val worklist = mutableListOf>() - worklist.add(listOf(this)) // We start only with the "from" node (=this) - - while (worklist.isNotEmpty()) { - val currentPath = worklist.removeFirst() - // The last node of the path is where we continue. We get all of its outgoing DFG edges and - // follow them - if (currentPath.last().nextEOGEdges.none { it.unreachable != true }) { - // No further nodes in the path and the path criteria are not satisfied. - failedPaths.add(currentPath) - continue // Don't add this path anymore. The requirement is satisfied. - } - - for (next in - currentPath.last().nextEOGEdges.filter { it.unreachable != true }.map { it.end }) { - // Copy the path for each outgoing DFG edge and add the next node - val nextPath = mutableListOf() - nextPath.addAll(currentPath) - nextPath.add(next) - if (predicate(next)) { - // We ended up in the node "to", so we're done. Add the path to the results. - fulfilledPaths.add(nextPath) - continue // Don't add this path anymore. The requirement is satisfied. - } - // The next node is new in the current path (i.e., there's no loop), so we add the path - // with the next step to the worklist. - if (!currentPath.contains(next)) { - worklist.add(nextPath) - } - } - } - - return FulfilledAndFailedPaths(fulfilledPaths, failedPaths) + return followXUntilHit( + x = { currentNode -> + currentNode.nextEOGEdges.filter { it.unreachable != true }.map { it.end } + }, + collectFailedPaths = true, + findAllPossiblePaths = true, + predicate = predicate + ) } /** @@ -359,45 +415,14 @@ fun Node.followNextEOGEdgesUntilHit(predicate: (Node) -> Boolean): FulfilledAndF * such a statement is always executed. */ fun Node.followPrevEOGEdgesUntilHit(predicate: (Node) -> Boolean): FulfilledAndFailedPaths { - // Looks complicated but at least it's not recursive... - // result: List of paths (between from and to) - val fulfilledPaths = mutableListOf>() - // failedPaths: All the paths which do not satisfy "predicate" - val failedPaths = mutableListOf>() - // The list of paths where we're not done yet. - val worklist = mutableListOf>() - worklist.add(listOf(this)) // We start only with the "from" node (=this) - - while (worklist.isNotEmpty()) { - val currentPath = worklist.removeFirst() - // The last node of the path is where we continue. We get all of its outgoing DFG edges and - // follow them - if (currentPath.last().prevEOGEdges.none { it.unreachable != true }) { - // No further nodes in the path and the path criteria are not satisfied. - failedPaths.add(currentPath) - continue // Don't add this path anymore. The requirement is satisfied. - } - - for (next in - currentPath.last().prevEOGEdges.filter { it.unreachable != true }.map { it.start }) { - // Copy the path for each outgoing DFG edge and add the next node - val nextPath = mutableListOf() - nextPath.addAll(currentPath) - nextPath.add(next) - if (predicate(next)) { - // We ended up in the node "to", so we're done. Add the path to the results. - fulfilledPaths.add(nextPath) - continue // Don't add this path anymore. The requirement is satisfied. - } - // The next node is new in the current path (i.e., there's no loop), so we add the path - // with the next step to the worklist. - if (!currentPath.contains(next)) { - worklist.add(nextPath) - } - } - } - - return FulfilledAndFailedPaths(fulfilledPaths, failedPaths) + return followXUntilHit( + x = { currentNode -> + currentNode.prevEOGEdges.filter { it.unreachable != true }.map { it.start } + }, + collectFailedPaths = true, + findAllPossiblePaths = true, + predicate = predicate + ) } /** @@ -634,7 +659,7 @@ fun Node.firstParentOrNull(predicate: (Node) -> Boolean): Node? { return node } - // go up-wards in the ast tree + // go upwards in the ast tree node = node.astParent } From bef69b63cd18e322d71dc322678b86e9193bbaac Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 11 Nov 2024 17:00:44 +0100 Subject: [PATCH 6/7] Test tuple lattice --- .../cpg/helpers/functional/BasicLattices.kt | 3 +- .../aisec/cpg/helpers/BasicLatticesTest.kt | 47 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/functional/BasicLattices.kt b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/functional/BasicLattices.kt index 11668449b9..f9e6aa85ba 100644 --- a/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/functional/BasicLattices.kt +++ b/cpg-core/src/main/kotlin/de/fraunhofer/aisec/cpg/helpers/functional/BasicLattices.kt @@ -79,7 +79,8 @@ class PowersetLattice(elements: Set) : LatticeElement>(elements) { } override fun equals(other: Any?): Boolean { - return other is PowersetLattice && this.elements == other.elements + // The call of `toSet` ensures that we don't get stuck for different types of sets. + return other is PowersetLattice && this.elements.toSet() == other.elements.toSet() } override fun hashCode(): Int { diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BasicLatticesTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BasicLatticesTest.kt index 6e2a5a86b2..bbfa8bbd4a 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BasicLatticesTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BasicLatticesTest.kt @@ -29,6 +29,7 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.helpers.functional.MapLattice import de.fraunhofer.aisec.cpg.helpers.functional.PowersetLattice import de.fraunhofer.aisec.cpg.helpers.functional.PowersetLatticeT +import de.fraunhofer.aisec.cpg.helpers.functional.TupleLattice import de.fraunhofer.aisec.cpg.helpers.functional.emptyMapLattice import de.fraunhofer.aisec.cpg.helpers.functional.emptyPowersetLattice import kotlin.test.Test @@ -36,6 +37,7 @@ import kotlin.test.assertEquals import kotlin.test.assertIs import kotlin.test.assertNotEquals import kotlin.test.assertNotSame +import kotlin.test.assertSame class BasicLatticesTest { @Test @@ -44,6 +46,7 @@ class BasicLatticesTest { val emptyLattice2 = emptyPowersetLattice() assertEquals(0, emptyLattice1.compareTo(emptyLattice2)) assertEquals(emptyLattice1, emptyLattice2) + assertNotSame(emptyLattice1.hashCode(), emptyLattice1.hashCode()) val blaLattice1 = PowersetLattice(setOf("bla")) val blaLattice2 = PowersetLattice(setOf("bla")) @@ -115,6 +118,7 @@ class BasicLatticesTest { val emptyLattice2 = emptyMapLattice>() assertEquals(0, emptyLattice1.compareTo(emptyLattice2)) assertEquals(emptyLattice1, emptyLattice2) + assertNotSame(emptyLattice1.hashCode(), emptyLattice1.hashCode()) val aBlaLattice1 = MapLattice>(mapOf("a" to PowersetLattice(setOf("bla")))) @@ -207,4 +211,47 @@ class BasicLatticesTest { assertEquals(setOf("bla", "foo"), aBlaFooBBla.elements["a"]?.elements) assertEquals(setOf("bla"), aBlaFooBBla.elements["b"]?.elements) } + + @Test + fun testPairLattice() { + val emptyEmpty = + TupleLattice, Set>( + Pair(emptyPowersetLattice(), emptyPowersetLattice()) + ) + val emptyBla = + TupleLattice, Set>( + Pair(emptyPowersetLattice(), PowersetLattice(setOf("bla"))) + ) + val blaEmpty = + TupleLattice, Set>( + Pair(PowersetLattice(setOf("bla")), emptyPowersetLattice()) + ) + val emptyBla2 = emptyBla.duplicate() + assertEquals(0, emptyBla.compareTo(emptyBla2)) + assertEquals(emptyBla, emptyBla2) + assertNotSame(emptyBla, emptyBla2) + assertNotSame(emptyBla.hashCode(), emptyBla2.hashCode()) + val (emptyBlaFirst, emptyBlaSecond) = emptyBla + assertSame(emptyBlaFirst, emptyBla.elements.first) + assertSame(emptyBlaSecond, emptyBla.elements.second) + assertNotSame(emptyBlaFirst, emptyBla2.elements.first) + assertEquals(emptyBlaFirst, emptyBla2.elements.first) + assertNotSame(emptyBlaSecond, emptyBla2.elements.second) + assertEquals(emptyBlaSecond, emptyBla2.elements.second) + + assertEquals(-1, emptyEmpty.compareTo(emptyBla)) + assertEquals(-1, emptyEmpty.compareTo(blaEmpty)) + assertEquals(1, emptyBla.compareTo(emptyEmpty)) + assertEquals(1, blaEmpty.compareTo(emptyEmpty)) + assertEquals(-1, blaEmpty.compareTo(emptyBla)) + assertEquals(-1, emptyBla.compareTo(blaEmpty)) + + val blaBla = emptyBla.lub(blaEmpty) + assertEquals(-1, emptyEmpty.compareTo(blaBla)) + assertEquals(-1, emptyBla.compareTo(blaBla)) + assertEquals(-1, blaEmpty.compareTo(blaBla)) + assertEquals(1, blaBla.compareTo(emptyEmpty)) + assertEquals(1, blaBla.compareTo(emptyBla)) + assertEquals(1, blaBla.compareTo(blaEmpty)) + } } From a5ff47ae27a4387e557d73bba7c96cbbbad8c454 Mon Sep 17 00:00:00 2001 From: Alexander Kuechler Date: Mon, 11 Nov 2024 17:14:12 +0100 Subject: [PATCH 7/7] Test triple lattice --- .../aisec/cpg/helpers/BasicLatticesTest.kt | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BasicLatticesTest.kt b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BasicLatticesTest.kt index bbfa8bbd4a..ad501c2007 100644 --- a/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BasicLatticesTest.kt +++ b/cpg-core/src/test/kotlin/de/fraunhofer/aisec/cpg/helpers/BasicLatticesTest.kt @@ -29,11 +29,13 @@ import de.fraunhofer.aisec.cpg.graph.Node import de.fraunhofer.aisec.cpg.helpers.functional.MapLattice import de.fraunhofer.aisec.cpg.helpers.functional.PowersetLattice import de.fraunhofer.aisec.cpg.helpers.functional.PowersetLatticeT +import de.fraunhofer.aisec.cpg.helpers.functional.TripleLattice import de.fraunhofer.aisec.cpg.helpers.functional.TupleLattice import de.fraunhofer.aisec.cpg.helpers.functional.emptyMapLattice import de.fraunhofer.aisec.cpg.helpers.functional.emptyPowersetLattice import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFalse import kotlin.test.assertIs import kotlin.test.assertNotEquals import kotlin.test.assertNotSame @@ -210,6 +212,10 @@ class BasicLatticesTest { assertEquals(setOf("a", "b"), aBlaFooBBla.elements.keys) assertEquals(setOf("bla", "foo"), aBlaFooBBla.elements["a"]?.elements) assertEquals(setOf("bla"), aBlaFooBBla.elements["b"]?.elements) + + assertFalse(aBlaFooBBla == emptyLattice1) // Wrong elements + assertFalse(aBlaFooBBla == aBlaFooBBla.elements["a"]) // Wrong types + assertFalse(aBlaFooBBla.elements["a"] == aBlaFooBBla) // Wrong types } @Test @@ -253,5 +259,85 @@ class BasicLatticesTest { assertEquals(1, blaBla.compareTo(emptyEmpty)) assertEquals(1, blaBla.compareTo(emptyBla)) assertEquals(1, blaBla.compareTo(blaEmpty)) + + // We explicitly want to call equals here + assertFalse(blaBla == emptyBla) // Wrong elements + assertFalse(blaBla == emptyBlaFirst) // Wrong types + assertFalse(emptyBlaFirst == blaBla) // Wrong types + } + + @Test + fun testTripleLattice() { + val emptyEmptyEmpty = + TripleLattice, Set, Set>( + Triple( + emptyPowersetLattice(), + emptyPowersetLattice(), + emptyPowersetLattice() + ) + ) + val emptyEmptyBla = + TripleLattice, Set, Set>( + Triple( + emptyPowersetLattice(), + emptyPowersetLattice(), + PowersetLattice(setOf("bla")) + ) + ) + val emptyBlaEmpty = + TripleLattice, Set, Set>( + Triple( + emptyPowersetLattice(), + PowersetLattice(setOf("bla")), + emptyPowersetLattice() + ) + ) + val blaEmptyEmpty = + TripleLattice, Set, Set>( + Triple( + PowersetLattice(setOf("bla")), + emptyPowersetLattice(), + emptyPowersetLattice() + ) + ) + val emptyBla2 = emptyEmptyBla.duplicate() + assertEquals(0, emptyEmptyBla.compareTo(emptyBla2)) + assertEquals(emptyEmptyBla, emptyBla2) + assertNotSame(emptyEmptyBla, emptyBla2) + assertNotSame(emptyEmptyBla.hashCode(), emptyBla2.hashCode()) + val (emptyBlaFirst, emptyBlaSecond, emptyBlaThird) = emptyEmptyBla + assertSame(emptyBlaFirst, emptyEmptyBla.elements.first) + assertSame(emptyBlaSecond, emptyEmptyBla.elements.second) + assertSame(emptyBlaThird, emptyEmptyBla.elements.third) + assertNotSame(emptyBlaFirst, emptyBla2.elements.first) + assertEquals(emptyBlaFirst, emptyBla2.elements.first) + assertNotSame(emptyBlaSecond, emptyBla2.elements.second) + assertEquals(emptyBlaSecond, emptyBla2.elements.second) + assertNotSame(emptyBlaThird, emptyBla2.elements.third) + assertEquals(emptyBlaThird, emptyBla2.elements.third) + + assertEquals(-1, emptyEmptyEmpty.compareTo(emptyEmptyBla)) + assertEquals(-1, emptyEmptyEmpty.compareTo(emptyBlaEmpty)) + assertEquals(-1, emptyEmptyEmpty.compareTo(blaEmptyEmpty)) + assertEquals(1, emptyEmptyBla.compareTo(emptyEmptyEmpty)) + assertEquals(1, blaEmptyEmpty.compareTo(emptyEmptyEmpty)) + assertEquals(1, emptyBlaEmpty.compareTo(emptyEmptyEmpty)) + assertEquals(-1, blaEmptyEmpty.compareTo(emptyEmptyBla)) + assertEquals(-1, emptyEmptyBla.compareTo(blaEmptyEmpty)) + assertEquals(-1, emptyBlaEmpty.compareTo(blaEmptyEmpty)) + + val blaEmptyBla = emptyEmptyBla.lub(blaEmptyEmpty) + assertEquals(-1, emptyEmptyEmpty.compareTo(blaEmptyBla)) + assertEquals(-1, emptyEmptyBla.compareTo(blaEmptyBla)) + assertEquals(-1, blaEmptyEmpty.compareTo(blaEmptyBla)) + assertEquals(-1, emptyBlaEmpty.compareTo(blaEmptyBla)) + assertEquals(1, blaEmptyBla.compareTo(emptyEmptyEmpty)) + assertEquals(1, blaEmptyBla.compareTo(emptyEmptyBla)) + assertEquals(1, blaEmptyBla.compareTo(blaEmptyEmpty)) + + // We explicitly want to call equals here + assertFalse(blaEmptyBla == emptyEmptyBla) // Wrong elements + assertFalse(blaEmptyBla == emptyBlaFirst) // Wrong types + assertFalse(emptyBlaFirst == blaEmptyBla) // Wrong types } }